#
tokens: 41009/50000 7/86 files (page 2/2)
lines: off (toggle) GitHub
raw markdown copy
This is page 2 of 2. Use http://codebase.md/leonardsellem/n8n-mcp-server?page={x} to view the full context.

# Directory Structure

```
├── .babelrc
├── .env.example
├── .eslintrc.json
├── .github
│   └── workflows
│       ├── claude-code-review.yml
│       ├── claude.yml
│       ├── docker-publish.yml
│       └── release-package.yml
├── .gitignore
├── AGENTS.md
├── babel.config.cjs
├── CLAUDE.md
├── Dockerfile
├── docs
│   ├── .gitkeep
│   ├── api
│   │   ├── dynamic-resources.md
│   │   ├── execution-tools.md
│   │   ├── index.md
│   │   ├── static-resources.md
│   │   └── workflow-tools.md
│   ├── development
│   │   ├── architecture.md
│   │   ├── extending.md
│   │   ├── index.md
│   │   └── testing.md
│   ├── examples
│   │   ├── advanced-scenarios.md
│   │   ├── basic-examples.md
│   │   ├── index.md
│   │   └── integration-examples.md
│   ├── images
│   │   ├── architecture.png.placeholder
│   │   └── n8n-api-key.png.placeholder
│   ├── index.md
│   └── setup
│       ├── configuration.md
│       ├── index.md
│       ├── installation.md
│       └── troubleshooting.md
├── jest.config.cjs
├── LICENSE
├── manual_verify_update.mjs
├── n8n-openapi.yml
├── package-lock.json
├── package.json
├── README.md
├── requirements.txt
├── run-tests.js
├── smithery.yaml
├── src
│   ├── .gitkeep
│   ├── api
│   │   ├── client.ts
│   │   └── n8n-client.ts
│   ├── config
│   │   ├── environment.ts
│   │   └── server.ts
│   ├── errors
│   │   ├── error-codes.ts
│   │   └── index.ts
│   ├── index.ts
│   ├── resources
│   │   ├── dynamic
│   │   │   ├── execution.ts
│   │   │   └── workflow.ts
│   │   ├── index.ts
│   │   └── static
│   │       ├── execution-stats.ts
│   │       └── workflows.ts
│   ├── tools
│   │   ├── execution
│   │   │   ├── base-handler.ts
│   │   │   ├── delete.ts
│   │   │   ├── get.ts
│   │   │   ├── handler.ts
│   │   │   ├── index.ts
│   │   │   ├── list.ts
│   │   │   └── run.ts
│   │   └── workflow
│   │       ├── activate.ts
│   │       ├── base-handler.ts
│   │       ├── create.ts
│   │       ├── deactivate.ts
│   │       ├── delete.ts
│   │       ├── get.ts
│   │       ├── handler.ts
│   │       ├── index.ts
│   │       ├── list.ts
│   │       └── update.ts
│   ├── types
│   │   └── index.ts
│   └── utils
│       ├── execution-formatter.ts
│       └── resource-formatter.ts
├── tests
│   ├── jest-globals.d.ts
│   ├── mocks
│   │   ├── axios-mock.ts
│   │   └── n8n-fixtures.ts
│   ├── README.md
│   ├── test-setup.ts
│   ├── tsconfig.json
│   └── unit
│       ├── api
│       │   ├── client.test.ts.bak
│       │   └── simple-client.test.ts
│       ├── config
│       │   ├── environment.test.ts
│       │   ├── environment.test.ts.bak
│       │   └── simple-environment.test.ts
│       ├── resources
│       │   └── dynamic
│       │       └── workflow.test.ts
│       ├── tools
│       │   └── workflow
│       │       ├── list.test.ts.bak
│       │       └── simple-tool.test.ts
│       └── utils
│           ├── execution-formatter.test.ts
│           └── resource-formatter.test.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/tests/unit/config/environment.test.ts:
--------------------------------------------------------------------------------

```typescript
import { jest, describe, test, expect, beforeEach, afterEach } from '@jest/globals';
import fs from 'fs';
import path from 'path';
import findConfig from 'find-config'; // Use the actual find-config

import { loadEnvironmentVariables } from '../../../src/config/environment';
import { ENV_VARS } from '../../../src/config/environment'; // To access defined var names

// Determine project root for placing dummy .env file
let projectRootDir: string | null = null;
let dummyEnvPath: string | null = null;

try {
  const packageJsonPath = findConfig('package.json');
  if (packageJsonPath) {
    projectRootDir = path.dirname(packageJsonPath);
    dummyEnvPath = path.resolve(projectRootDir, '.env.test_dummy'); // Use a distinct name
  } else {
    console.error("Could not find project root (package.json). Tests involving .env file might fail or be skipped.");
  }
} catch (e) {
  console.error("Error finding project root:", e);
}


const originalEnv = { ...process.env };

const clearTestEnvVars = () => {
  delete process.env[ENV_VARS.N8N_API_URL];
  delete process.env[ENV_VARS.N8N_API_KEY];
  delete process.env[ENV_VARS.N8N_WEBHOOK_USERNAME];
  delete process.env[ENV_VARS.N8N_WEBHOOK_PASSWORD];
};

const saveEnvState = () => {
  return { ...process.env };
};

const DUMMY_ENV_CONTENT = `
${ENV_VARS.N8N_API_URL}=http://dummyapi.com
${ENV_VARS.N8N_API_KEY}=dummyapikey
${ENV_VARS.N8N_WEBHOOK_USERNAME}=dummyuser
${ENV_VARS.N8N_WEBHOOK_PASSWORD}=dummypassword
`;

describe('loadEnvironmentVariables', () => {
  beforeEach(() => {
    jest.resetModules(); // Reset module cache, critical for dotenv
    process.env = { ...originalEnv }; // Restore original env
    clearTestEnvVars(); // Clear our specific vars

    // Ensure dummy .env is clean before each test that might create it
    if (dummyEnvPath && fs.existsSync(dummyEnvPath)) {
      fs.unlinkSync(dummyEnvPath);
    }
  });

  afterEach(() => {
    // Restore original env
    process.env = { ...originalEnv };
    // Clean up dummy .env file if it exists after a test
    if (dummyEnvPath && fs.existsSync(dummyEnvPath)) {
      try {
        fs.unlinkSync(dummyEnvPath);
      } catch (e) {
        // In case the test itself deleted it, or it was never created.
      }
    }
  });

  // Test Case 1: All environment variables set
  test('should not change process.env if all required env vars are already set', () => {
    process.env[ENV_VARS.N8N_API_URL] = 'http://existingapi.com';
    process.env[ENV_VARS.N8N_API_KEY] = 'existingapikey';
    process.env[ENV_VARS.N8N_WEBHOOK_USERNAME] = 'existinguser';
    process.env[ENV_VARS.N8N_WEBHOOK_PASSWORD] = 'existingpassword';

    const envStateBeforeLoad = saveEnvState();
    loadEnvironmentVariables(); // Should not load .env because vars are present
    
    expect(process.env).toEqual(envStateBeforeLoad);
  });

  // Test Case 2: No environment variables set, .env file exists
  test('should load vars from .env file if no required env vars are set and .env exists', () => {
    if (!projectRootDir || !dummyEnvPath) {
      console.warn("Skipping test: Project root not found, cannot create dummy .env file.");
      return; // or expect.hasAssertions() with a different path
    }
    
    // Pre-condition: specific vars are not set (cleared in beforeEach)
    expect(process.env[ENV_VARS.N8N_API_URL]).toBeUndefined();

    fs.writeFileSync(dummyEnvPath, DUMMY_ENV_CONTENT);

    // Temporarily point findConfig's "idea" of .env to our dummy .env by hijacking process.env for dotenv
    // This is tricky because loadEnvironmentVariables uses findConfig to locate package.json, then path.resolve for .env
    // The most straightforward way is to ensure findConfig returns our projectRootDir, and then dotenv loads our dummyEnvPath.
    // The current implementation of loadEnvironmentVariables is:
    //   const projectRoot = findConfig('package.json');
    //   if (projectRoot) {
    //     const envPath = path.resolve(path.dirname(projectRoot), '.env'); <--- This is the key
    //     dotenv.config({ path: envPath });
    //   }
    // So, for this test, we need to make `path.resolve` point to `dummyEnvPath` OR make the actual `.env` the dummy.
    // Let's rename the actual .env if it exists, place our dummy, then restore.
    // A simpler approach for testing: the function loads ".env". So we make our dummy file THE ".env".
    
    const actualEnvPath = path.resolve(projectRootDir, '.env');
    let actualEnvRenamedPath: string | null = null;
    if (fs.existsSync(actualEnvPath)) {
      actualEnvRenamedPath = actualEnvPath + '.backup';
      fs.renameSync(actualEnvPath, actualEnvRenamedPath);
    }

    fs.writeFileSync(actualEnvPath, DUMMY_ENV_CONTENT); // Write our dummy content to the actual .env path

    try {
      loadEnvironmentVariables();

      expect(process.env[ENV_VARS.N8N_API_URL]).toBe('http://dummyapi.com');
      expect(process.env[ENV_VARS.N8N_API_KEY]).toBe('dummyapikey');
      expect(process.env[ENV_VARS.N8N_WEBHOOK_USERNAME]).toBe('dummyuser');
      expect(process.env[ENV_VARS.N8N_WEBHOOK_PASSWORD]).toBe('dummypassword');
    } finally {
      // Clean up: remove our dummy .env and restore original .env if it was renamed
      if (fs.existsSync(actualEnvPath)) {
        fs.unlinkSync(actualEnvPath);
      }
      if (actualEnvRenamedPath && fs.existsSync(actualEnvRenamedPath)) {
        fs.renameSync(actualEnvRenamedPath, actualEnvPath);
      }
    }
  });

  // Test Case 3: No environment variables set, no .env file exists
  test('should not change process.env if no required env vars are set and no .env file exists', () => {
    if (!projectRootDir) {
      console.warn("Skipping parts of test: Project root not found, .env file check might be unreliable.");
      // We can still proceed as findConfig would return null or the .env file wouldn't be found
    } else {
      const actualEnvPath = path.resolve(projectRootDir, '.env');
      if (fs.existsSync(actualEnvPath)) {
        // This test requires no .env file, so if one exists (e.g. a real one for dev), this test is harder.
        // For CI/isolated env, it should be fine. Here we assume it's okay if it doesn't exist.
        // If it *does* exist, the test might reflect that it *was* loaded if not handled.
        console.warn(`Warning: Test 'no .env file exists' running when an actual .env file is present at ${actualEnvPath}. This test assumes it won't be loaded or is empty.`);
        // To be robust, we'd need to ensure it's not there, similar to Test Case 2's cleanup.
        // For now, we assume `loadEnvironmentVariables` won't find one if `findConfig` fails or the file is empty/irrelevant.
      }
    }
    
    // Vars are cleared in beforeEach
    const envStateBeforeLoad = saveEnvState();
    loadEnvironmentVariables(); // Should not find a .env file to load (or findConfig returns null)
    
    expect(process.env[ENV_VARS.N8N_API_URL]).toBeUndefined();
    expect(process.env[ENV_VARS.N8N_API_KEY]).toBeUndefined();
    expect(process.env[ENV_VARS.N8N_WEBHOOK_USERNAME]).toBeUndefined();
    expect(process.env[ENV_VARS.N8N_WEBHOOK_PASSWORD]).toBeUndefined();
    // Check if other env vars were not disturbed (more robust check)
    expect(process.env).toEqual(envStateBeforeLoad);
  });

  // Test Case 4: Some environment variables set
  test('should not change process.env if some (but not all) required env vars are set', () => {
    process.env[ENV_VARS.N8N_API_URL] = 'http://partialapi.com';
    process.env[ENV_VARS.N8N_API_KEY] = 'partialapikey';
    // N8N_WEBHOOK_USERNAME and N8N_WEBHOOK_PASSWORD are not set (cleared by beforeEach)

    const envStateBeforeLoad = saveEnvState();
    loadEnvironmentVariables(); // Should not load .env because some vars are present
    
    expect(process.env).toEqual(envStateBeforeLoad);
    expect(process.env[ENV_VARS.N8N_API_URL]).toBe('http://partialapi.com');
    expect(process.env[ENV_VARS.N8N_API_KEY]).toBe('partialapikey');
    expect(process.env[ENV_VARS.N8N_WEBHOOK_USERNAME]).toBeUndefined();
    expect(process.env[ENV_VARS.N8N_WEBHOOK_PASSWORD]).toBeUndefined();
  });
});

```

--------------------------------------------------------------------------------
/src/api/client.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * n8n API Client
 * 
 * This module provides a client for interacting with the n8n API.
 */

import axios, { AxiosInstance } from 'axios';
import { EnvConfig } from '../config/environment.js';
import { handleAxiosError, N8nApiError } from '../errors/index.js';

/**
 * n8n API Client class for making requests to the n8n API
 */
export class N8nApiClient {
  private axiosInstance: AxiosInstance;
  private config: EnvConfig;

  /**
   * Create a new n8n API client
   * 
   * @param config Environment configuration
   */
  constructor(config: EnvConfig) {
    this.config = config;
    this.axiosInstance = axios.create({
      baseURL: config.n8nApiUrl,
      headers: {
        'X-N8N-API-KEY': config.n8nApiKey,
        'Accept': 'application/json',
      },
      timeout: 10000, // 10 seconds
    });

    // Add request debugging if debug mode is enabled
    if (config.debug) {
      this.axiosInstance.interceptors.request.use(request => {
        console.error(`[DEBUG] Request: ${request.method?.toUpperCase()} ${request.url}`);
        return request;
      });

      this.axiosInstance.interceptors.response.use(response => {
        console.error(`[DEBUG] Response: ${response.status} ${response.statusText}`);
        return response;
      });
    }
  }

  /**
   * Check connectivity to the n8n API
   * 
   * @returns Promise that resolves if connectivity check succeeds
   * @throws N8nApiError if connectivity check fails
   */
  async checkConnectivity(): Promise<void> {
    try {
      // Try to fetch health endpoint or workflows
      const response = await this.axiosInstance.get('/workflows');
      
      if (response.status !== 200) {
        throw new N8nApiError(
          'n8n API connectivity check failed',
          response.status
        );
      }
      
      if (this.config.debug) {
        console.error(`[DEBUG] Successfully connected to n8n API at ${this.config.n8nApiUrl}`);
        console.error(`[DEBUG] Found ${response.data.data?.length || 0} workflows`);
      }
    } catch (error) {
      throw handleAxiosError(error, 'Failed to connect to n8n API');
    }
  }

  /**
   * Get the axios instance for making custom requests
   * 
   * @returns Axios instance
   */
  getAxiosInstance(): AxiosInstance {
    return this.axiosInstance;
  }

  /**
   * Get all workflows from n8n
   * 
   * @returns Array of workflow objects
   */
  async getWorkflows(): Promise<any[]> {
    try {
      const response = await this.axiosInstance.get('/workflows');
      return response.data.data || [];
    } catch (error) {
      throw handleAxiosError(error, 'Failed to fetch workflows');
    }
  }

  /**
   * Get a specific workflow by ID
   * 
   * @param id Workflow ID
   * @returns Workflow object
   */
  async getWorkflow(id: string): Promise<any> {
    try {
      const response = await this.axiosInstance.get(`/workflows/${id}`);
      return response.data;
    } catch (error) {
      throw handleAxiosError(error, `Failed to fetch workflow ${id}`);
    }
  }

  /**
   * Get all workflow executions
   * 
   * @returns Array of execution objects
   */
  async getExecutions(): Promise<any[]> {
    try {
      const response = await this.axiosInstance.get('/executions');
      return response.data.data || [];
    } catch (error) {
      throw handleAxiosError(error, 'Failed to fetch executions');
    }
  }

  /**
   * Get a specific execution by ID
   * 
   * @param id Execution ID
   * @returns Execution object
   */
  async getExecution(id: string): Promise<any> {
    try {
      const response = await this.axiosInstance.get(`/executions/${id}`);
      return response.data;
    } catch (error) {
      throw handleAxiosError(error, `Failed to fetch execution ${id}`);
    }
  }

  /**
   * Execute a workflow by ID
   * 
   * @param id Workflow ID
   * @param data Optional data to pass to the workflow
   * @returns Execution result
   */
  async executeWorkflow(id: string, data?: Record<string, any>): Promise<any> {
    try {
      const response = await this.axiosInstance.post(`/workflows/${id}/execute`, data || {});
      return response.data;
    } catch (error) {
      throw handleAxiosError(error, `Failed to execute workflow ${id}`);
    }
  }

  /**
   * Create a new workflow
   * 
   * @param workflow Workflow object to create
   * @returns Created workflow
   */
  async createWorkflow(workflow: Record<string, any>): Promise<any> {
    try {
      // Make sure settings property is present
      if (!workflow.settings) {
        workflow.settings = {
          saveExecutionProgress: true,
          saveManualExecutions: true,
          saveDataErrorExecution: "all",
          saveDataSuccessExecution: "all",
          executionTimeout: 3600,
          timezone: "UTC"
        };
      }
      
      // Remove read-only properties that cause issues
      const workflowToCreate = { ...workflow };
      delete workflowToCreate.active; // Remove active property as it's read-only
      delete workflowToCreate.id; // Remove id property if it exists
      delete workflowToCreate.createdAt; // Remove createdAt property if it exists
      delete workflowToCreate.updatedAt; // Remove updatedAt property if it exists
      delete workflowToCreate.tags; // Remove tags property as it's read-only
      
      // Log request for debugging
      console.error('[DEBUG] Creating workflow with data:', JSON.stringify(workflowToCreate, null, 2));
      
      const response = await this.axiosInstance.post('/workflows', workflowToCreate);
      return response.data;
    } catch (error) {
      console.error('[ERROR] Create workflow error:', error);
      throw handleAxiosError(error, 'Failed to create workflow');
    }
  }

  /**
   * Update an existing workflow
   * 
   * @param id Workflow ID
   * @param workflow Updated workflow object
   * @returns Updated workflow
   */
  async updateWorkflow(id: string, workflow: Record<string, any>): Promise<any> {
    try {
      // Remove read-only properties that cause issues with n8n API v1
      // According to n8n API schema, only name, nodes, connections, settings, and staticData are allowed
      const workflowToUpdate = { ...workflow };
      delete workflowToUpdate.id; // Remove id property as it's read-only
      delete workflowToUpdate.active; // Remove active property as it's read-only
      delete workflowToUpdate.createdAt; // Remove createdAt property as it's read-only
      delete workflowToUpdate.updatedAt; // Remove updatedAt property as it's read-only
      delete workflowToUpdate.tags; // Remove tags property as it's read-only

      // Log request for debugging
      if (this.config.debug) {
        console.error('[DEBUG] Updating workflow with data:', JSON.stringify(workflowToUpdate, null, 2));
      }

      const response = await this.axiosInstance.put(`/workflows/${id}`, workflowToUpdate);
      return response.data;
    } catch (error) {
      throw handleAxiosError(error, `Failed to update workflow ${id}`);
    }
  }

  /**
   * Delete a workflow
   * 
   * @param id Workflow ID
   * @returns Deleted workflow
   */
  async deleteWorkflow(id: string): Promise<any> {
    try {
      const response = await this.axiosInstance.delete(`/workflows/${id}`);
      return response.data;
    } catch (error) {
      throw handleAxiosError(error, `Failed to delete workflow ${id}`);
    }
  }

  /**
   * Activate a workflow
   * 
   * @param id Workflow ID
   * @returns Activated workflow
   */
  async activateWorkflow(id: string): Promise<any> {
    try {
      const response = await this.axiosInstance.post(`/workflows/${id}/activate`);
      return response.data;
    } catch (error) {
      throw handleAxiosError(error, `Failed to activate workflow ${id}`);
    }
  }

  /**
   * Deactivate a workflow
   * 
   * @param id Workflow ID
   * @returns Deactivated workflow
   */
  async deactivateWorkflow(id: string): Promise<any> {
    try {
      const response = await this.axiosInstance.post(`/workflows/${id}/deactivate`);
      return response.data;
    } catch (error) {
      throw handleAxiosError(error, `Failed to deactivate workflow ${id}`);
    }
  }
  
  /**
   * Delete an execution
   * 
   * @param id Execution ID
   * @returns Deleted execution or success message
   */
  async deleteExecution(id: string): Promise<any> {
    try {
      const response = await this.axiosInstance.delete(`/executions/${id}`);
      return response.data;
    } catch (error) {
      throw handleAxiosError(error, `Failed to delete execution ${id}`);
    }
  }
}

/**
 * Create and return a configured n8n API client
 * 
 * @param config Environment configuration
 * @returns n8n API client instance
 */
export function createApiClient(config: EnvConfig): N8nApiClient {
  return new N8nApiClient(config);
}

```

--------------------------------------------------------------------------------
/docs/development/testing.md:
--------------------------------------------------------------------------------

```markdown
# Testing

This document describes the testing approach for the n8n MCP Server and provides guidelines for writing effective tests.

## Overview

The n8n MCP Server uses Jest as its testing framework and follows a multi-level testing approach:

1. **Unit Tests**: Test individual components in isolation
2. **Integration Tests**: Test interactions between components
3. **End-to-End Tests**: Test the entire system as a whole

Tests are organized in the `tests/` directory, with a structure that mirrors the `src/` directory.

## Running Tests

### Running All Tests

To run all tests:

```bash
npm test
```

This command runs all tests and outputs a summary of the results.

### Running Tests with Coverage

To run tests with coverage reporting:

```bash
npm run test:coverage
```

This generates coverage reports in the `coverage/` directory, including HTML reports that you can view in a browser.

### Running Tests in Watch Mode

During development, you can run tests in watch mode, which will automatically rerun tests when files change:

```bash
npm run test:watch
```

### Running Specific Tests

To run tests in a specific file or directory:

```bash
npx jest path/to/test-file.test.ts
```

Or to run tests matching a specific pattern:

```bash
npx jest -t "test pattern"
```

## Test Structure

Tests are organized into the following directories:

- `tests/unit/`: Unit tests for individual components
- `tests/integration/`: Integration tests that test interactions between components
- `tests/e2e/`: End-to-end tests that test the entire system
- `tests/mocks/`: Shared test fixtures and mocks

### Unit Tests

Unit tests are organized in a structure that mirrors the `src/` directory. For example:

- `src/api/n8n-client.ts` has a corresponding test at `tests/unit/api/n8n-client.test.ts`
- `src/tools/workflow/list.ts` has a corresponding test at `tests/unit/tools/workflow/list.test.ts`

### Integration Tests

Integration tests focus on testing interactions between components, such as:

- Testing that tools correctly use the API client
- Testing that resources correctly format data from the API

### End-to-End Tests

End-to-end tests test the entire system, from the transport layer to the API client and back.

## Writing Effective Tests

### Unit Test Example

Here's an example of a unit test for a workflow tool:

```typescript
// tests/unit/tools/workflow/list.test.ts
import { describe, it, expect, jest } from '@jest/globals';
import { getListWorkflowsToolDefinition, handleListWorkflows } from '../../../../src/tools/workflow/list.js';
import { N8nClient } from '../../../../src/api/n8n-client.js';

// Mock data
const mockWorkflows = [
  {
    id: '1234abc',
    name: 'Test Workflow 1',
    active: true,
    createdAt: '2025-03-01T12:00:00.000Z',
    updatedAt: '2025-03-02T14:30:00.000Z'
  },
  {
    id: '5678def',
    name: 'Test Workflow 2',
    active: false,
    createdAt: '2025-03-01T12:00:00.000Z',
    updatedAt: '2025-03-12T10:15:00.000Z'
  }
];

describe('Workflow List Tool', () => {
  describe('getListWorkflowsToolDefinition', () => {
    it('should return the correct tool definition', () => {
      const definition = getListWorkflowsToolDefinition();
      
      expect(definition.name).toBe('workflow_list');
      expect(definition.description).toBeTruthy();
      expect(definition.inputSchema).toBeDefined();
      expect(definition.inputSchema.properties).toHaveProperty('active');
      expect(definition.inputSchema.required).toEqual([]);
    });
  });
  
  describe('handleListWorkflows', () => {
    it('should return all workflows when no filter is provided', async () => {
      // Mock the API client
      const mockClient = {
        getWorkflows: jest.fn().mockResolvedValue(mockWorkflows)
      };
      
      const result = await handleListWorkflows(mockClient as unknown as N8nClient, {});
      
      expect(mockClient.getWorkflows).toHaveBeenCalledWith(undefined);
      expect(result.isError).toBeFalsy();
      
      // Parse the JSON text to check the content
      const content = JSON.parse(result.content[0].text);
      expect(content).toHaveLength(2);
      expect(content[0].id).toBe('1234abc');
      expect(content[1].id).toBe('5678def');
    });
    
    it('should filter workflows by active status', async () => {
      // Mock the API client
      const mockClient = {
        getWorkflows: jest.fn().mockResolvedValue(mockWorkflows)
      };
      
      const result = await handleListWorkflows(mockClient as unknown as N8nClient, { active: true });
      
      expect(mockClient.getWorkflows).toHaveBeenCalledWith(true);
      expect(result.isError).toBeFalsy();
      
      // Parse the JSON text to check the content
      const content = JSON.parse(result.content[0].text);
      expect(content).toHaveLength(2);
    });
    
    it('should handle API errors', async () => {
      // Mock the API client to throw an error
      const mockClient = {
        getWorkflows: jest.fn().mockRejectedValue(new Error('API error'))
      };
      
      const result = await handleListWorkflows(mockClient as unknown as N8nClient, {});
      
      expect(result.isError).toBeTruthy();
      expect(result.content[0].text).toContain('API error');
    });
  });
});
```

### Integration Test Example

Here's an example of an integration test that tests the interaction between a resource handler and the API client:

```typescript
// tests/integration/resources/static/workflows.test.ts
import { describe, it, expect, jest } from '@jest/globals';
import { handleWorkflowsRequest, WORKFLOWS_URI } from '../../../../src/resources/static/workflows.js';
import { N8nClient } from '../../../../src/api/n8n-client.js';

// Mock data
const mockWorkflows = [
  {
    id: '1234abc',
    name: 'Test Workflow 1',
    active: true,
    createdAt: '2025-03-01T12:00:00.000Z',
    updatedAt: '2025-03-02T14:30:00.000Z'
  },
  {
    id: '5678def',
    name: 'Test Workflow 2',
    active: false,
    createdAt: '2025-03-01T12:00:00.000Z',
    updatedAt: '2025-03-12T10:15:00.000Z'
  }
];

describe('Workflows Resource Handler', () => {
  it('should return a properly formatted response', async () => {
    // Mock the API client
    const mockClient = {
      getWorkflows: jest.fn().mockResolvedValue(mockWorkflows)
    };
    
    const response = await handleWorkflowsRequest(mockClient as unknown as N8nClient);
    
    expect(mockClient.getWorkflows).toHaveBeenCalled();
    expect(response.contents).toHaveLength(1);
    expect(response.contents[0].uri).toBe(WORKFLOWS_URI);
    expect(response.contents[0].mimeType).toBe('application/json');
    
    // Parse the JSON text to check the content
    const content = JSON.parse(response.contents[0].text);
    expect(content).toHaveProperty('workflows');
    expect(content.workflows).toHaveLength(2);
    expect(content.count).toBe(2);
    expect(content.workflows[0].id).toBe('1234abc');
  });
  
  it('should handle API errors', async () => {
    // Mock the API client to throw an error
    const mockClient = {
      getWorkflows: jest.fn().mockRejectedValue(new Error('API error'))
    };
    
    await expect(handleWorkflowsRequest(mockClient as unknown as N8nClient))
      .rejects
      .toThrow('Failed to retrieve workflows');
  });
});
```

### End-to-End Test Example

Here's an example of an end-to-end test that tests the entire system:

```typescript
// tests/e2e/workflow-operations.test.ts
import { describe, it, expect, beforeAll, afterAll } from '@jest/globals';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { MemoryServerTransport } from '@modelcontextprotocol/sdk/server/memory.js';
import { createServer } from '../../src/index.js';

describe('End-to-End Workflow Operations', () => {
  let server: Server;
  let transport: MemoryServerTransport;
  
  beforeAll(async () => {
    // Mock the environment
    process.env.N8N_API_URL = 'http://localhost:5678/api/v1';
    process.env.N8N_API_KEY = 'test-api-key';
    
    // Create the server with a memory transport
    transport = new MemoryServerTransport();
    server = await createServer(transport);
  });
  
  afterAll(async () => {
    await server.close();
  });
  
  it('should list workflows', async () => {
    // Send a request to list workflows
    const response = await transport.sendRequest({
      jsonrpc: '2.0',
      id: '1',
      method: 'callTool',
      params: {
        name: 'workflow_list',
        arguments: {}
      }
    });
    
    expect(response.result).toBeDefined();
    expect(response.result.content).toHaveLength(1);
    expect(response.result.content[0].type).toBe('text');
    
    // Parse the JSON text to check the content
    const content = JSON.parse(response.result.content[0].text);
    expect(Array.isArray(content)).toBe(true);
  });
  
  it('should retrieve a workflow by ID', async () => {
    // Send a request to get a workflow
    const response = await transport.sendRequest({
      jsonrpc: '2.0',
      id: '2',
      method: 'callTool',
      params: {
        name: 'workflow_get',
        arguments: {
          id: '1234abc'
        }
      }
    });
    
    expect(response.result).toBeDefined();
    expect(response.result.content).toHaveLength(1);
    expect(response.result.content[0].type).toBe('text');
    
    // Parse the JSON text to check the content
    const content = JSON.parse(response.result.content[0].text);
    expect(content).toHaveProperty('id');
    expect(content.id).toBe('1234abc');
  });
});
```

## Test Fixtures and Mocks

To avoid duplication and improve test maintainability, common test fixtures and mocks are stored in the `tests/mocks/` directory.

### Axios Mock

The Axios HTTP client is mocked using `axios-mock-adapter` to simulate HTTP responses without making actual API calls:

```typescript
// tests/mocks/axios-mock.ts
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';

// Create a new instance of the mock adapter
export const axiosMock = new MockAdapter(axios);

// Helper function to reset the mock adapter before each test
export function resetAxiosMock() {
  axiosMock.reset();
}
```

### n8n API Fixtures

Common fixtures for n8n API responses are stored in a shared file:

```typescript
// tests/mocks/n8n-fixtures.ts
export const mockWorkflows = [
  {
    id: '1234abc',
    name: 'Test Workflow 1',
    active: true,
    createdAt: '2025-03-01T12:00:00.000Z',
    updatedAt: '2025-03-02T14:30:00.000Z',
    nodes: [
      {
        id: 'node1',
        name: 'Start',
        type: 'n8n-nodes-base.start',
        position: [100, 200],
        parameters: {}
      }
    ],
    connections: {}
  },
  {
    id: '5678def',
    name: 'Test Workflow 2',
    active: false,
    createdAt: '2025-03-01T12:00:00.000Z',
    updatedAt: '2025-03-12T10:15:00.000Z',
    nodes: [],
    connections: {}
  }
];

export const mockExecutions = [
  {
    id: 'exec123',
    workflowId: '1234abc',
    workflowName: 'Test Workflow 1',
    status: 'success',
    startedAt: '2025-03-10T15:00:00.000Z',
    finishedAt: '2025-03-10T15:01:00.000Z',
    mode: 'manual'
  },
  {
    id: 'exec456',
    workflowId: '1234abc',
    workflowName: 'Test Workflow 1',
    status: 'error',
    startedAt: '2025-03-09T12:00:00.000Z',
    finishedAt: '2025-03-09T12:00:10.000Z',
    mode: 'manual'
  }
];
```

## Test Environment

The test environment is configured in `jest.config.js` and `babel.config.js`. Key configurations include:

- TypeScript support via Babel
- ES module support
- Coverage reporting

The `tests/test-setup.ts` file contains global setup code that runs before tests:

```typescript
// tests/test-setup.ts
import { jest } from '@jest/globals';
import { resetAxiosMock } from './mocks/axios-mock';

// Reset mocks before each test
beforeEach(() => {
  jest.clearAllMocks();
  resetAxiosMock();
});
```

## Best Practices

### General Testing Guidelines

1. **Write tests first**: Follow a test-driven development (TDD) approach when possible.
2. **Test behavior, not implementation**: Focus on what a component does, not how it's implemented.
3. **Keep tests simple**: Each test should test one behavior or aspect of functionality.
4. **Use descriptive test names**: Test names should describe the expected behavior.
5. **Follow the AAA pattern**: Arrange, Act, Assert (setup, execute, verify).

### Mocking Best Practices

1. **Mock dependencies, not the unit under test**: Only mock external dependencies, not the code you're testing.
2. **Use the minimum viable mock**: Only mock the methods and behavior needed for the test.
3. **Ensure mock behavior is realistic**: Mocks should behave similarly to the real implementation.
4. **Verify interactions with mocks**: Use `expect(mock).toHaveBeenCalled()` to verify interactions.

### Error Testing Best Practices

1. **Test error cases**: Don't just test the happy path; test error handling too.
2. **Simulate errors with mocks**: Use mocks to simulate error scenarios.
3. **Verify error messages**: Ensure error messages are helpful and descriptive.

### Performance Testing Considerations

1. **Monitor test performance**: Slow tests can slow down development.
2. **Use test timeout values wisely**: Set appropriate timeout values for async tests.
3. **Minimize redundant setup**: Use `beforeEach` and `beforeAll` to avoid redundant setup.

## Continuous Integration

Tests are run automatically in CI environments on pull requests and commits to the main branch. The CI configuration ensures tests pass before code can be merged.

### CI Test Requirements

- All tests must pass
- Test coverage must not decrease
- Linting checks must pass

## Debugging Tests

### Console Output

You can use `console.log()` statements in your tests to debug issues:

```typescript
it('should do something', () => {
  const result = doSomething();
  console.log('Result:', result);
  expect(result).toBe(expectedValue);
});
```

When running tests with Jest, console output will be displayed for failing tests by default.

### Using the Debugger

You can also use the Node.js debugger with Jest:

```bash
node --inspect-brk node_modules/.bin/jest --runInBand path/to/test
```

Then connect to the debugger with Chrome DevTools or VS Code.

## Conclusion

Thorough testing is essential for maintaining a reliable and robust n8n MCP Server. By following these guidelines and examples, you can write effective tests that help ensure your code works as expected and catches issues early.

```

--------------------------------------------------------------------------------
/docs/development/extending.md:
--------------------------------------------------------------------------------

```markdown
# Extending the Server

This guide explains how to extend the n8n MCP Server with new functionality.

## Overview

The n8n MCP Server is designed to be extensible, allowing developers to add new tools and resources without modifying existing code. This extensibility makes it easy to support new n8n features or customize the server for specific use cases.

## Adding a New Tool

Tools in the MCP server represent executable operations that AI assistants can use. To add a new tool, follow these steps:

### 1. Define the Tool Interface

Create a new TypeScript interface that defines the input parameters for your tool:

```typescript
// src/types/tools/my-tool.ts
export interface MyToolParams {
  param1: string;
  param2?: number; // Optional parameter
}
```

### 2. Create the Tool Handler

Create a new file for your tool in the appropriate category under `src/tools/`:

```typescript
// src/tools/category/my-tool.ts
import { ToolCallResponse, ToolDefinition } from '@modelcontextprotocol/sdk/types.js';
import { N8nClient } from '../../api/n8n-client.js';
import { MyToolParams } from '../../types/tools/my-tool.js';

// Define the tool
export function getMyToolDefinition(): ToolDefinition {
  return {
    name: 'my_tool',
    description: 'Description of what my tool does',
    inputSchema: {
      type: 'object',
      properties: {
        param1: {
          type: 'string',
          description: 'Description of param1'
        },
        param2: {
          type: 'number',
          description: 'Description of param2'
        }
      },
      required: ['param1']
    }
  };
}

// Implement the tool handler
export async function handleMyTool(
  client: N8nClient,
  params: MyToolParams
): Promise<ToolCallResponse> {
  try {
    // Implement the tool logic here
    // Use the N8nClient to interact with n8n

    // Return the response
    return {
      content: [
        {
          type: 'text',
          text: 'Result of the operation'
        }
      ]
    };
  } catch (error) {
    // Handle errors
    return {
      content: [
        {
          type: 'text',
          text: `Error: ${error.message}`
        }
      ],
      isError: true
    };
  }
}
```

### 3. Register the Tool in the Handler

Update the main handler file for your tool category (e.g., `src/tools/category/handler.ts`):

```typescript
// src/tools/category/handler.ts
import { getMyToolDefinition, handleMyTool } from './my-tool.js';

// Add your tool to the tools object
export const categoryTools = {
  // ... existing tools
  my_tool: {
    definition: getMyToolDefinition,
    handler: handleMyTool
  }
};
```

### 4. Add Handler to Main Server

Update the main tool handler registration in `src/index.ts`:

```typescript
// src/index.ts
import { categoryTools } from './tools/category/handler.js';

// In the server initialization
const server = new Server(
  {
    name: 'n8n-mcp-server',
    version: '0.1.0'
  },
  {
    capabilities: {
      tools: {
        // ... existing categories
        category: true
      }
    }
  }
);

// Register tool handlers
Object.entries(categoryTools).forEach(([name, { definition, handler }]) => {
  server.setToolHandler(definition(), async (request) => {
    return await handler(client, request.params.arguments as any);
  });
});
```

### 5. Add Unit Tests

Create unit tests for your new tool:

```typescript
// tests/unit/tools/category/my-tool.test.ts
import { describe, it, expect, jest } from '@jest/globals';
import { getMyToolDefinition, handleMyTool } from '../../../../src/tools/category/my-tool.js';

describe('My Tool', () => {
  describe('getMyToolDefinition', () => {
    it('should return the correct tool definition', () => {
      const definition = getMyToolDefinition();
      
      expect(definition.name).toBe('my_tool');
      expect(definition.description).toBeTruthy();
      expect(definition.inputSchema).toBeDefined();
      expect(definition.inputSchema.properties).toHaveProperty('param1');
      expect(definition.inputSchema.required).toEqual(['param1']);
    });
  });
  
  describe('handleMyTool', () => {
    it('should handle valid parameters', async () => {
      const mockClient = {
        // Mock the necessary client methods
      };
      
      const result = await handleMyTool(mockClient as any, {
        param1: 'test value'
      });
      
      expect(result.isError).toBeFalsy();
      expect(result.content[0].text).toBeTruthy();
    });
    
    it('should handle errors properly', async () => {
      const mockClient = {
        // Mock client that throws an error
        someMethod: jest.fn().mockRejectedValue(new Error('Test error'))
      };
      
      const result = await handleMyTool(mockClient as any, {
        param1: 'test value'
      });
      
      expect(result.isError).toBeTruthy();
      expect(result.content[0].text).toContain('Error');
    });
  });
});
```

## Adding a New Resource

Resources in the MCP server provide data access through URI-based templates. To add a new resource, follow these steps:

### 1. Create a Static Resource (No Parameters)

For a resource that doesn't require parameters:

```typescript
// src/resources/static/my-resource.ts
import { McpError, ReadResourceResponse } from '@modelcontextprotocol/sdk/types.js';
import { ErrorCode } from '../../errors/error-codes.js';
import { N8nClient } from '../../api/n8n-client.js';

export const MY_RESOURCE_URI = 'n8n://my-resource';

export async function handleMyResourceRequest(
  client: N8nClient
): Promise<ReadResourceResponse> {
  try {
    // Implement the resource logic
    // Use the N8nClient to interact with n8n
    
    // Return the response
    return {
      contents: [
        {
          uri: MY_RESOURCE_URI,
          mimeType: 'application/json',
          text: JSON.stringify(
            {
              // Resource data
              property1: 'value1',
              property2: 'value2'
            },
            null,
            2
          )
        }
      ]
    };
  } catch (error) {
    throw new McpError(
      ErrorCode.InternalError,
      `Failed to retrieve resource: ${error.message}`
    );
  }
}
```

### 2. Create a Dynamic Resource (With Parameters)

For a resource that requires parameters:

```typescript
// src/resources/dynamic/my-resource.ts
import { McpError, ReadResourceResponse } from '@modelcontextprotocol/sdk/types.js';
import { ErrorCode } from '../../errors/error-codes.js';
import { N8nClient } from '../../api/n8n-client.js';

export const MY_RESOURCE_URI_TEMPLATE = 'n8n://my-resource/{id}';

export function matchMyResourceUri(uri: string): { id: string } | null {
  const match = uri.match(/^n8n:\/\/my-resource\/([^/]+)$/);
  if (!match) return null;
  
  return {
    id: decodeURIComponent(match[1])
  };
}

export async function handleMyResourceRequest(
  client: N8nClient,
  uri: string
): Promise<ReadResourceResponse> {
  const params = matchMyResourceUri(uri);
  if (!params) {
    throw new McpError(
      ErrorCode.InvalidRequest,
      `Invalid URI format: ${uri}`
    );
  }
  
  try {
    // Implement the resource logic using params.id
    // Use the N8nClient to interact with n8n
    
    // Return the response
    return {
      contents: [
        {
          uri,
          mimeType: 'application/json',
          text: JSON.stringify(
            {
              // Resource data with the specific ID
              id: params.id,
              property1: 'value1',
              property2: 'value2'
            },
            null,
            2
          )
        }
      ]
    };
  } catch (error) {
    throw new McpError(
      ErrorCode.InternalError,
      `Failed to retrieve resource: ${error.message}`
    );
  }
}
```

### 3. Register Resources in the Handler Files

Update the resource handler registration:

#### For Static Resources

```typescript
// src/resources/static/index.ts
import { MY_RESOURCE_URI, handleMyResourceRequest } from './my-resource.js';

export const staticResources = {
  // ... existing static resources
  [MY_RESOURCE_URI]: handleMyResourceRequest
};
```

#### For Dynamic Resources

```typescript
// src/resources/dynamic/index.ts
import { MY_RESOURCE_URI_TEMPLATE, matchMyResourceUri, handleMyResourceRequest } from './my-resource.js';

export const dynamicResourceMatchers = [
  // ... existing dynamic resource matchers
  {
    uriTemplate: MY_RESOURCE_URI_TEMPLATE,
    match: matchMyResourceUri,
    handler: handleMyResourceRequest
  }
];
```

### 4. Add Resource Listings

Update the resource listing functions:

```typescript
// src/resources/index.ts
// Update the resource templates listing
export function getResourceTemplates() {
  return [
    // ... existing templates
    {
      uriTemplate: MY_RESOURCE_URI_TEMPLATE,
      name: 'My Resource',
      description: 'Description of my resource'
    }
  ];
}

// Update the static resources listing
export function getStaticResources() {
  return [
    // ... existing resources
    {
      uri: MY_RESOURCE_URI,
      name: 'My Resource List',
      description: 'List of all my resources'
    }
  ];
}
```

### 5. Add Unit Tests

Create tests for your new resource:

```typescript
// tests/unit/resources/static/my-resource.test.ts
// or
// tests/unit/resources/dynamic/my-resource.test.ts
import { describe, it, expect, jest } from '@jest/globals';
import {
  MY_RESOURCE_URI,
  handleMyResourceRequest
} from '../../../../src/resources/static/my-resource.js';

describe('My Resource', () => {
  it('should return resource data', async () => {
    const mockClient = {
      // Mock the necessary client methods
    };
    
    const response = await handleMyResourceRequest(mockClient as any);
    
    expect(response.contents).toHaveLength(1);
    expect(response.contents[0].uri).toBe(MY_RESOURCE_URI);
    expect(response.contents[0].mimeType).toBe('application/json');
    
    const data = JSON.parse(response.contents[0].text);
    expect(data).toHaveProperty('property1');
    expect(data).toHaveProperty('property2');
  });
  
  it('should handle errors properly', async () => {
    const mockClient = {
      // Mock client that throws an error
      someMethod: jest.fn().mockRejectedValue(new Error('Test error'))
    };
    
    await expect(handleMyResourceRequest(mockClient as any))
      .rejects
      .toThrow('Failed to retrieve resource');
  });
});
```

## Extending the API Client

If you need to add support for new n8n API features, extend the N8nClient class:

### 1. Add New Methods to the Client

```typescript
// src/api/n8n-client.ts
export class N8nClient {
  // ... existing methods
  
  // Add new methods
  async myNewApiMethod(param1: string): Promise<any> {
    try {
      const response = await this.httpClient.get(`/endpoint/${param1}`);
      return response.data;
    } catch (error) {
      this.handleApiError(error);
    }
  }
}
```

### 2. Add Type Definitions

```typescript
// src/types/api.ts
// Add types for API responses and requests
export interface MyApiResponse {
  id: string;
  name: string;
  // Other properties
}

export interface MyApiRequest {
  param1: string;
  param2?: number;
}
```

### 3. Add Tests for the New API Methods

```typescript
// tests/unit/api/n8n-client.test.ts
describe('N8nClient', () => {
  // ... existing tests
  
  describe('myNewApiMethod', () => {
    it('should call the correct API endpoint', async () => {
      // Set up mock Axios
      axiosMock.onGet('/endpoint/test').reply(200, {
        id: '123',
        name: 'Test'
      });
      
      const client = new N8nClient({
        apiUrl: 'http://localhost:5678/api/v1',
        apiKey: 'test-api-key'
      });
      
      const result = await client.myNewApiMethod('test');
      
      expect(result).toEqual({
        id: '123',
        name: 'Test'
      });
    });
    
    it('should handle errors correctly', async () => {
      // Set up mock Axios
      axiosMock.onGet('/endpoint/test').reply(404, {
        message: 'Not found'
      });
      
      const client = new N8nClient({
        apiUrl: 'http://localhost:5678/api/v1',
        apiKey: 'test-api-key'
      });
      
      await expect(client.myNewApiMethod('test'))
        .rejects
        .toThrow('Resource not found');
    });
  });
});
```

## Best Practices for Extensions

1. **Follow the Existing Patterns**: Try to follow the patterns already established in the codebase.
2. **Type Safety**: Use TypeScript types and interfaces to ensure type safety.
3. **Error Handling**: Implement comprehensive error handling in all extensions.
4. **Testing**: Write thorough tests for all new functionality.
5. **Documentation**: Document your extensions, including JSDoc comments for all public methods.
6. **Backward Compatibility**: Ensure that your extensions don't break existing functionality.

## Example: Adding Support for n8n Tags

Here's a complete example of adding support for n8n tags:

### API Client Extension

```typescript
// src/api/n8n-client.ts
export class N8nClient {
  // ... existing methods
  
  // Add tag methods
  async getTags(): Promise<Tag[]> {
    try {
      const response = await this.httpClient.get('/tags');
      return response.data;
    } catch (error) {
      this.handleApiError(error);
    }
  }
  
  async createTag(data: CreateTagRequest): Promise<Tag> {
    try {
      const response = await this.httpClient.post('/tags', data);
      return response.data;
    } catch (error) {
      this.handleApiError(error);
    }
  }
  
  async deleteTag(id: string): Promise<void> {
    try {
      await this.httpClient.delete(`/tags/${id}`);
    } catch (error) {
      this.handleApiError(error);
    }
  }
}
```

### Type Definitions

```typescript
// src/types/api.ts
export interface Tag {
  id: string;
  name: string;
  createdAt: string;
  updatedAt: string;
}

export interface CreateTagRequest {
  name: string;
}
```

### Tool Implementations

```typescript
// src/tools/tag/list.ts
export function getTagListToolDefinition(): ToolDefinition {
  return {
    name: 'tag_list',
    description: 'List all tags in n8n',
    inputSchema: {
      type: 'object',
      properties: {},
      required: []
    }
  };
}

export async function handleTagList(
  client: N8nClient,
  params: any
): Promise<ToolCallResponse> {
  try {
    const tags = await client.getTags();
    
    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify(tags, null, 2)
        }
      ]
    };
  } catch (error) {
    return {
      content: [
        {
          type: 'text',
          text: `Error listing tags: ${error.message}`
        }
      ],
      isError: true
    };
  }
}
```

### Resource Implementation

```typescript
// src/resources/static/tags.ts
export const TAGS_URI = 'n8n://tags';

export async function handleTagsRequest(
  client: N8nClient
): Promise<ReadResourceResponse> {
  try {
    const tags = await client.getTags();
    
    return {
      contents: [
        {
          uri: TAGS_URI,
          mimeType: 'application/json',
          text: JSON.stringify(
            {
              tags,
              count: tags.length
            },
            null,
            2
          )
        }
      ]
    };
  } catch (error) {
    throw new McpError(
      ErrorCode.InternalError,
      `Failed to retrieve tags: ${error.message}`
    );
  }
}
```

### Integration

Register the new tools and resources in the appropriate handler files, and update the main server initialization to include them.

By following these patterns, you can extend the n8n MCP Server to support any n8n feature or add custom functionality tailored to your specific needs.

```

--------------------------------------------------------------------------------
/docs/examples/advanced-scenarios.md:
--------------------------------------------------------------------------------

```markdown
# Advanced Scenarios

This page demonstrates more complex examples of using the n8n MCP Server with AI assistants.

## Workflow Analysis and Optimization

### User Prompt

"Analyze my workflows and suggest optimizations."

### Assistant Actions

```javascript
// Get all workflows
const workflows = await useMcpTool('n8n-mcp-server', 'workflow_list', {});

// Get execution statistics
const stats = await accessMcpResource('n8n-mcp-server', 'n8n://execution-stats');

// Analyze each workflow in detail
const analysisResults = [];

for (const workflow of workflows) {
  // Get detailed workflow information
  const details = await useMcpTool('n8n-mcp-server', 'workflow_get', {
    id: workflow.id
  });
  
  // Get recent executions
  const executions = await useMcpTool('n8n-mcp-server', 'execution_list', {
    workflowId: workflow.id,
    limit: 10
  });
  
  // Analyze workflow structure
  const nodeCount = details.nodes.length;
  const nodeTypes = new Set(details.nodes.map(node => node.type));
  
  // Check for error patterns in executions
  const errorRate = executions.length > 0 
    ? executions.filter(e => e.status === "error").length / executions.length 
    : 0;
  
  // Check execution times
  const executionTimes = executions
    .filter(e => e.status === "success" && e.finishedAt && e.startedAt)
    .map(e => new Date(e.finishedAt) - new Date(e.startedAt));
  
  const avgExecutionTime = executionTimes.length > 0
    ? executionTimes.reduce((sum, time) => sum + time, 0) / executionTimes.length / 1000
    : 0;
  
  // Generate optimization suggestions
  const suggestions = [];
  
  if (nodeCount > 15) {
    suggestions.push("Consider splitting into multiple workflows for better maintainability");
  }
  
  if (errorRate > 0.2) {
    suggestions.push("High error rate detected - review error handling and input validation");
  }
  
  if (avgExecutionTime > 30) {
    suggestions.push("Long execution time - check for bottlenecks or inefficient operations");
  }
  
  if (nodeTypes.has("n8n-nodes-base.httpRequest") && !nodeTypes.has("n8n-nodes-base.errorTrigger")) {
    suggestions.push("HTTP requests without error handling - consider adding error triggers");
  }
  
  analysisResults.push({
    id: workflow.id,
    name: workflow.name,
    nodeCount,
    errorRate,
    avgExecutionTime,
    suggestions
  });
}

// Format and present the analysis
let response = "# Workflow Analysis and Optimization Suggestions\n\n";

analysisResults.forEach(result => {
  response += `## ${result.name}\n\n`;
  response += `**Nodes:** ${result.nodeCount}\n`;
  response += `**Error Rate:** ${(result.errorRate * 100).toFixed(1)}%\n`;
  response += `**Avg. Execution Time:** ${result.avgExecutionTime.toFixed(1)} seconds\n\n`;
  
  if (result.suggestions.length > 0) {
    response += "**Optimization Suggestions:**\n";
    result.suggestions.forEach(suggestion => {
      response += `- ${suggestion}\n`;
    });
  } else {
    response += "No specific optimization suggestions for this workflow.\n";
  }
  
  response += "\n";
});

return response;
```

## Scheduled Workflow Management

### User Prompt

"Update all my scheduled workflows to run one hour later."

### Assistant Actions

```javascript
// Get all workflows
const workflows = await useMcpTool('n8n-mcp-server', 'workflow_list', {});

// Filter for scheduled workflows (those with a Schedule node)
const scheduledWorkflows = [];

for (const workflow of workflows) {
  // Get detailed workflow information
  const details = await useMcpTool('n8n-mcp-server', 'workflow_get', {
    id: workflow.id
  });
  
  // Look for Schedule Trigger nodes
  const scheduleNodes = details.nodes.filter(node => 
    node.type === "n8n-nodes-base.scheduleTrigger" || 
    node.type === "n8n-nodes-base.cron"
  );
  
  if (scheduleNodes.length > 0) {
    scheduledWorkflows.push({
      workflow: details,
      scheduleNodes
    });
  }
}

if (scheduledWorkflows.length === 0) {
  return "I couldn't find any workflows with scheduled triggers.";
}

// Process each scheduled workflow
const results = [];

for (const { workflow, scheduleNodes } of scheduledWorkflows) {
  // Create a copy of the workflow for modification
  const updatedWorkflow = { ...workflow };
  
  // Update each schedule node
  for (const scheduleNode of scheduleNodes) {
    const nodeIndex = updatedWorkflow.nodes.findIndex(n => n.id === scheduleNode.id);
    
    if (nodeIndex === -1) continue;
    
    // Copy the node for modification
    const updatedNode = { ...updatedWorkflow.nodes[nodeIndex] };
    
    // Handle different types of schedule configurations
    if (updatedNode.type === "n8n-nodes-base.scheduleTrigger") {
      if (updatedNode.parameters.cronExpression) {
        // Modify cron expression to run 1 hour later
        const cronParts = updatedNode.parameters.cronExpression.split(' ');
        if (cronParts.length === 5) {
          // Standard cron format: minute hour day month dayOfWeek
          const hour = parseInt(cronParts[1], 10);
          cronParts[1] = ((hour + 1) % 24).toString();
          updatedNode.parameters.cronExpression = cronParts.join(' ');
        }
      } else if (updatedNode.parameters.timeToRepeat) {
        // Handle specific time scheduling
        const time = updatedNode.parameters.timeToRepeat;
        if (time && time.split(':').length === 2) {
          const [hours, minutes] = time.split(':').map(part => parseInt(part, 10));
          const newHours = (hours + 1) % 24;
          updatedNode.parameters.timeToRepeat = `${newHours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
        }
      }
    } else if (updatedNode.type === "n8n-nodes-base.cron") {
      // Similar handling for cron node
      if (updatedNode.parameters.cronExpression) {
        const cronParts = updatedNode.parameters.cronExpression.split(' ');
        if (cronParts.length === 5) {
          const hour = parseInt(cronParts[1], 10);
          cronParts[1] = ((hour + 1) % 24).toString();
          updatedNode.parameters.cronExpression = cronParts.join(' ');
        }
      }
    }
    
    // Update the node in the workflow
    updatedWorkflow.nodes[nodeIndex] = updatedNode;
  }
  
  // Save the updated workflow
  const result = await useMcpTool('n8n-mcp-server', 'workflow_update', {
    id: workflow.id,
    nodes: updatedWorkflow.nodes,
    connections: updatedWorkflow.connections
  });
  
  results.push({
    name: workflow.name,
    updated: result.updatedAt
  });
}

// Format and present the results
let response = "# Updated Scheduled Workflows\n\n";
response += "I've updated the following workflows to run one hour later:\n\n";

results.forEach(result => {
  response += `- **${result.name}** (Updated: ${new Date(result.updated).toLocaleString()})\n`;
});

return response;
```

## Error Handling and Notification System

### User Prompt

"Set up a notification system for any workflow that fails."

### Assistant Actions

```javascript
// This scenario demonstrates creating a monitoring workflow that checks for failures
// and sends notifications

// First, check if a monitoring workflow already exists
const workflows = await useMcpTool('n8n-mcp-server', 'workflow_list', {});
const existingMonitor = workflows.find(workflow => 
  workflow.name.toLowerCase().includes('workflow monitor') || 
  workflow.name.toLowerCase().includes('error notification')
);

if (existingMonitor) {
  return `You already have a monitoring workflow: "${existingMonitor.name}" (ID: ${existingMonitor.id}). Would you like me to update it instead?`;
}

// Create a new monitoring workflow
const monitorWorkflow = await useMcpTool('n8n-mcp-server', 'workflow_create', {
  name: "Workflow Error Notification System",
  active: false, // Start inactive until configured
  nodes: [
    {
      name: "Schedule Trigger",
      type: "n8n-nodes-base.scheduleTrigger",
      position: [100, 300],
      parameters: {
        cronExpression: "*/15 * * * *" // Run every 15 minutes
      }
    },
    {
      name: "Get Failed Executions",
      type: "n8n-nodes-base.n8n",
      position: [300, 300],
      parameters: {
        resource: "execution",
        operation: "getAll",
        filters: {
          status: "error",
          // Look for executions in the last 15 minutes
          finished: {
            $gt: "={{$now.minus({ minutes: 15 }).toISOString()}}"
          }
        }
      }
    },
    {
      name: "Filter Empty",
      type: "n8n-nodes-base.filter",
      position: [500, 300],
      parameters: {
        conditions: {
          boolean: [
            {
              value1: "={{ $json.length > 0 }}",
              operation: "equal",
              value2: true
            }
          ]
        }
      }
    },
    {
      name: "Format Notification",
      type: "n8n-nodes-base.function",
      position: [700, 300],
      parameters: {
        functionCode: `
// Function to format error notifications
const executions = items;
const now = new Date();

// Group by workflow
const workflowErrors = {};
for (const execution of executions) {
  const workflowId = execution.workflowId;
  const workflowName = execution.workflowData.name;
  
  if (!workflowErrors[workflowId]) {
    workflowErrors[workflowId] = {
      name: workflowName,
      errors: []
    };
  }
  
  workflowErrors[workflowId].errors.push({
    id: execution.id,
    time: execution.finished,
    error: execution.error?.message || "Unknown error"
  });
}

// Create notification text
let notificationText = "⚠️ Workflow Error Alert ⚠️\\n\\n";
notificationText += "The following workflows have failed:\\n\\n";

for (const [workflowId, data] of Object.entries(workflowErrors)) {
  notificationText += \`👉 \${data.name} (ID: \${workflowId})\\n\`;
  notificationText += \`   Failed executions: \${data.errors.length}\\n\`;
  
  // Add details about each failure
  data.errors.forEach(error => {
    const time = new Date(error.time).toLocaleString();
    notificationText += \`   - \${time}: \${error.error}\\n\`;
  });
  
  notificationText += "\\n";
}

notificationText += "Check your n8n dashboard for more details.";

return [{
  json: {
    text: notificationText,
    subject: \`n8n Alert: \${Object.keys(workflowErrors).length} Workflow(s) Failed\`,
    timestamp: now.toISOString()
  }
}];
`
      }
    },
    {
      name: "Send Email",
      type: "n8n-nodes-base.emailSend",
      position: [900, 300],
      parameters: {
        to: "{{$env.EMAIL_RECIPIENT}}", // Will need to be configured
        subject: "{{$json.subject}}",
        text: "{{$json.text}}"
      }
    }
  ],
  connections: {
    "Schedule Trigger": {
      main: [
        [
          {
            node: "Get Failed Executions",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "Get Failed Executions": {
      main: [
        [
          {
            node: "Filter Empty",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "Filter Empty": {
      main: [
        [
          {
            node: "Format Notification",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "Format Notification": {
      main: [
        [
          {
            node: "Send Email",
            type: "main",
            index: 0
          }
        ]
      ]
    }
  }
});

return `
# Workflow Error Notification System Created

I've created a new workflow that will monitor for failed executions and send email notifications.

**Workflow ID:** ${monitorWorkflow.id}

## Configuration Needed:
1. The workflow is currently **inactive** - you'll need to activate it once configured
2. Set up the email configuration in n8n settings
3. Configure the "Send Email" node with your recipient email address

## How It Works:
- Runs every 15 minutes
- Checks for any workflows that failed in the last 15 minutes
- Groups failures by workflow and formats a notification
- Sends an email with details about the failures

You can customize the notification format, schedule, and notification method by editing the workflow in the n8n interface.
`;
```

## Data Migration Between Workflows

### User Prompt

"Help me move data from my 'Legacy CRM' workflow to my 'New CRM' workflow."

### Assistant Actions

```javascript
// Get all workflows
const workflows = await useMcpTool('n8n-mcp-server', 'workflow_list', {});

// Find the source and target workflows
const legacyWorkflow = workflows.find(w => w.name.toLowerCase().includes('legacy crm'));
const newWorkflow = workflows.find(w => w.name.toLowerCase().includes('new crm'));

if (!legacyWorkflow) {
  return "I couldn't find a workflow with 'Legacy CRM' in the name. Please check the exact name of your source workflow.";
}

if (!newWorkflow) {
  return "I couldn't find a workflow with 'New CRM' in the name. Please check the exact name of your target workflow.";
}

// Create a data migration workflow
const migrationWorkflow = await useMcpTool('n8n-mcp-server', 'workflow_create', {
  name: "Data Migration: Legacy CRM to New CRM",
  active: false, // Start inactive until confirmed
  nodes: [
    {
      name: "Manual Trigger",
      type: "n8n-nodes-base.manualTrigger",
      position: [100, 300],
      parameters: {}
    },
    {
      name: "Execute Legacy Workflow",
      type: "n8n-nodes-base.executeWorkflow",
      position: [300, 300],
      parameters: {
        workflowId: legacyWorkflow.id,
        options: {
          includeData: true
        }
      }
    },
    {
      name: "Transform Data",
      type: "n8n-nodes-base.function",
      position: [500, 300],
      parameters: {
        functionCode: `
// This is a placeholder transformation function that you'll need to customize
// based on the actual data structure of your workflows

const legacyData = items;
const transformedItems = [];

// Example transformation (modify based on your data structures)
for (const item of legacyData) {
  transformedItems.push({
    json: {
      // Map legacy fields to new fields
      customer_id: item.json.id,
      customer_name: item.json.fullName || \`\${item.json.firstName || ''} \${item.json.lastName || ''}\`.trim(),
      email: item.json.emailAddress || item.json.email,
      phone: item.json.phoneNumber || item.json.phone,
      notes: item.json.comments || item.json.notes || '',
      // Add migration metadata
      migrated_from_legacy: true,
      migration_date: new Date().toISOString()
    }
  });
}

return transformedItems;
`
      }
    },
    {
      name: "Execute New Workflow",
      type: "n8n-nodes-base.executeWorkflow",
      position: [700, 300],
      parameters: {
        workflowId: newWorkflow.id,
        options: {
          includeData: true
        }
      }
    },
    {
      name: "Migration Summary",
      type: "n8n-nodes-base.function",
      position: [900, 300],
      parameters: {
        functionCode: `
// Create a summary of the migration
const results = items;
const totalItems = items.length;
const successItems = items.filter(item => !item.json.error).length;
const errorItems = totalItems - successItems;

return [
  {
    json: {
      summary: "Migration Complete",
      total_records: totalItems,
      successful_records: successItems,
      failed_records: errorItems,
      completion_time: new Date().toISOString()
    }
  }
];
`
      }
    }
  ],
  connections: {
    "Manual Trigger": {
      main: [
        [
          {
            node: "Execute Legacy Workflow",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "Execute Legacy Workflow": {
      main: [
        [
          {
            node: "Transform Data",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "Transform Data": {
      main: [
        [
          {
            node: "Execute New Workflow",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "Execute New Workflow": {
      main: [
        [
          {
            node: "Migration Summary",
            type: "main",
            index: 0
          }
        ]
      ]
    }
  }
});

return `
# Data Migration Workflow Created

I've created a new workflow to migrate data from "${legacyWorkflow.name}" to "${newWorkflow.name}".

**Migration Workflow ID:** ${migrationWorkflow.id}

## Important Notes:
1. The workflow is currently **inactive** - activate it only when you're ready to perform the migration
2. The data transformation is a placeholder - you'll need to edit the "Transform Data" function node to map fields correctly based on your specific data structures
3. This is a one-time migration workflow - run it manually when you're ready to migrate the data

## Next Steps:
1. Open the workflow in the n8n interface
2. Edit the "Transform Data" function to correctly map your data fields
3. Test the workflow with a small sample if possible
4. Activate and run the workflow to perform the migration
5. Check the migration summary for results

Would you like me to help you customize the data transformation based on the specific fields in your CRM workflows?
`;
```

These examples demonstrate more advanced usage patterns for the n8n MCP Server. For integration with other systems, see the [Integration Examples](./integration-examples.md) page.

```

--------------------------------------------------------------------------------
/docs/examples/integration-examples.md:
--------------------------------------------------------------------------------

```markdown
# Integration Examples

This page provides examples of integrating the n8n MCP Server with other systems and AI assistant platforms.

## AI Assistant Integration Examples

### Claude AI Assistant Integration

#### Example: Setting Up n8n MCP Server with Claude

```javascript
// Register the n8n MCP Server with Claude using the MCP Installer
const installationResult = await useMcpTool('mcp-installer', 'install_repo_mcp_server', {
  name: 'n8n-mcp-server',
  env: [
    "N8N_API_URL=http://localhost:5678/api/v1",
    "N8N_API_KEY=your_n8n_api_key_here",
    "DEBUG=false"
  ]
});

// Once registered, Claude can interact with n8n
// Here's an example conversation:

// User: "Show me my active workflows in n8n"
// Claude: (uses workflow_list tool to retrieve and display active workflows)

// User: "Execute my 'Daily Report' workflow"
// Claude: (uses execution_run tool to start the workflow and provide status)
```

#### Example: Using n8n to Extend Claude's Capabilities

```javascript
// Using n8n as a bridge to external systems 
// This example shows how Claude can access a database through n8n

// User: "Show me the top 5 customers from my database"
// Claude would:

// 1. Find the appropriate workflow
const workflows = await useMcpTool('n8n-mcp-server', 'workflow_list', {});
const dbQueryWorkflow = workflows.find(w => 
  w.name.toLowerCase().includes('database query') || 
  w.name.toLowerCase().includes('db query')
);

if (!dbQueryWorkflow) {
  return "I couldn't find a database query workflow. Would you like me to help you create one?";
}

// 2. Execute the workflow with the appropriate parameters
const execution = await useMcpTool('n8n-mcp-server', 'execution_run', {
  workflowId: dbQueryWorkflow.id,
  data: {
    query: "SELECT * FROM customers ORDER BY total_purchases DESC LIMIT 5",
    format: "table"
  },
  waitForCompletion: true
});

// 3. Present the results to the user
if (execution.status !== "success") {
  return "There was an error querying the database. Error: " + (execution.error || "Unknown error");
}

// Format the results as a table
const customers = execution.data.resultData.runData.lastNode[0].data.json;
let response = "# Top 5 Customers\n\n";
response += "| Customer Name | Email | Total Purchases |\n";
response += "|--------------|-------|----------------|\n";

customers.forEach(customer => {
  response += `| ${customer.name} | ${customer.email} | $${customer.total_purchases.toFixed(2)} |\n`;
});

return response;
```

### OpenAI Assistant Integration

#### Example: Connecting n8n MCP Server to OpenAI Assistant

```javascript
// This is a conceptual example of how an OpenAI Assistant might interact with the n8n MCP Server

// In an OpenAI Assistant Function definition:
{
  "name": "n8n_workflow_list",
  "description": "List all workflows in n8n",
  "parameters": {
    "type": "object",
    "properties": {
      "active": {
        "type": "boolean",
        "description": "Filter by active status (optional)"
      }
    }
  }
}

// The function would call the n8n MCP Server:
async function n8n_workflow_list(params) {
  // Call the n8n MCP Server API
  const response = await fetch('http://localhost:3000/n8n-mcp/tools/workflow_list', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer YOUR_AUTH_TOKEN'
    },
    body: JSON.stringify(params)
  });
  
  return await response.json();
}

// The OpenAI Assistant would then use this function when asked about n8n workflows
```

## Integration with External Systems

### n8n to Git Integration

#### Example: Using n8n MCP Server to Manage Workflow Versioning in Git

```javascript
// User request: "Backup all my workflows to my Git repository"

// The assistant first creates a workflow for Git backup
const backupWorkflow = await useMcpTool('n8n-mcp-server', 'workflow_create', {
  name: "Workflow Git Backup",
  active: false,
  nodes: [
    {
      name: "Manual Trigger",
      type: "n8n-nodes-base.manualTrigger",
      position: [100, 300],
      parameters: {}
    },
    {
      name: "Get All Workflows",
      type: "n8n-nodes-base.n8n",
      position: [300, 300],
      parameters: {
        resource: "workflow",
        operation: "getAll"
      }
    },
    {
      name: "Format For Git",
      type: "n8n-nodes-base.function",
      position: [500, 300],
      parameters: {
        functionCode: `
// Convert workflows to individual JSON files
const workflows = items;
const outputItems = [];

for (const workflow of workflows) {
  // Create a sanitized filename
  const filename = workflow.json.name
    .replace(/[^a-zA-Z0-9]/g, '_')
    .toLowerCase() + '.json';
  
  outputItems.push({
    json: {
      filename: filename,
      content: JSON.stringify(workflow.json, null, 2),
      commit_message: \`Backup workflow: \${workflow.json.name}\`
    }
  });
}

return outputItems;
`
      }
    },
    {
      name: "Git Commit",
      type: "n8n-nodes-base.git",
      position: [700, 300],
      parameters: {
        repository: "{{$env.GIT_REPO_PATH}}",
        directory: "workflows",
        operation: "commit",
        message: "Automated workflow backup {{$now.format('YYYY-MM-DD HH:mm')}}",
        filePath: "={{$json.filename}}",
        fileContent: "={{$json.content}}"
      }
    },
    {
      name: "Git Push",
      type: "n8n-nodes-base.git",
      position: [900, 300],
      parameters: {
        repository: "{{$env.GIT_REPO_PATH}}",
        operation: "push"
      }
    }
  ],
  connections: {
    "Manual Trigger": {
      main: [
        [
          {
            node: "Get All Workflows",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "Get All Workflows": {
      main: [
        [
          {
            node: "Format For Git",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "Format For Git": {
      main: [
        [
          {
            node: "Git Commit",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "Git Commit": {
      main: [
        [
          {
            node: "Git Push",
            type: "main",
            index: 0
          }
        ]
      ]
    }
  }
});

return `
# Git Backup Workflow Created

I've created a new workflow that will backup all your n8n workflows to Git.

**Workflow ID:** ${backupWorkflow.id}

## Configuration Needed:
1. The workflow is currently **inactive** - activate it once configured
2. Set the environment variable \`GIT_REPO_PATH\` to point to your Git repository
3. Ensure the Git repository is properly configured with credentials

## How It Works:
- When executed, this workflow retrieves all n8n workflows
- Converts each workflow to a JSON file
- Commits each file to the specified Git repository
- Pushes the changes to the remote repository

## Usage:
You can run this workflow manually whenever you want to backup your workflows,
or add a Schedule Trigger to automate regular backups.
`;
```

### CRM Integration

#### Example: Setting Up a Two-Way Sync Between n8n and a CRM

```javascript
// User request: "Help me set up a sync between n8n and my HubSpot CRM"

// First check for existing credentials
const workflows = await useMcpTool('n8n-mcp-server', 'workflow_list', {});
const hubspotWorkflows = workflows.filter(w => 
  w.name.toLowerCase().includes('hubspot')
);

let credentialsMessage = "";
if (hubspotWorkflows.length > 0) {
  credentialsMessage = "I noticed you already have HubSpot workflows. I'll assume you have HubSpot credentials set up.";
} else {
  credentialsMessage = "You'll need to set up HubSpot credentials in n8n before activating this workflow.";
}

// Create the sync workflow
const syncWorkflow = await useMcpTool('n8n-mcp-server', 'workflow_create', {
  name: "HubSpot Two-Way Sync",
  active: false,
  nodes: [
    {
      name: "Schedule Trigger",
      type: "n8n-nodes-base.scheduleTrigger",
      position: [100, 300],
      parameters: {
        cronExpression: "0 */2 * * *" // Every 2 hours
      }
    },
    {
      name: "Get HubSpot Contacts",
      type: "n8n-nodes-base.hubspot",
      position: [300, 200],
      parameters: {
        resource: "contact",
        operation: "getAll",
        returnAll: true,
        additionalFields: {
          formattedDate: true
        }
      }
    },
    {
      name: "Get n8n Contacts",
      type: "n8n-nodes-base.function",
      position: [300, 400],
      parameters: {
        functionCode: `
// This function would retrieve contacts from your internal database
// For example purposes, we're using a placeholder
// In reality, you might use another n8n node to fetch from your database

return [
  {
    json: {
      internalContacts: [
        // Example internal contacts
        // In a real scenario, these would come from your database
      ]
    }
  }
];
`
      }
    },
    {
      name: "Compare & Identify Changes",
      type: "n8n-nodes-base.function",
      position: [500, 300],
      parameters: {
        functionCode: `
const hubspotContacts = $input.first();
const internalContacts = $input.last().json.internalContacts;

// Identify new/updated contacts in HubSpot
const hubspotUpdates = hubspotContacts.json.map(contact => {
  // Find matching internal contact by email
  const internalMatch = internalContacts.find(
    ic => ic.email === contact.properties.email
  );
  
  if (!internalMatch) {
    return {
      json: {
        type: 'new_in_hubspot',
        contact: contact,
        action: 'create_in_internal'
      }
    };
  }
  
  // Check if HubSpot contact is newer
  const hubspotUpdated = new Date(contact.properties.lastmodifieddate);
  const internalUpdated = new Date(internalMatch.updated_at);
  
  if (hubspotUpdated > internalUpdated) {
    return {
      json: {
        type: 'updated_in_hubspot',
        contact: contact,
        action: 'update_in_internal'
      }
    };
  }
  
  return null;
}).filter(item => item !== null);

// Identify new/updated contacts in internal system
const internalUpdates = internalContacts.map(contact => {
  // Find matching HubSpot contact by email
  const hubspotMatch = hubspotContacts.json.find(
    hc => hc.properties.email === contact.email
  );
  
  if (!hubspotMatch) {
    return {
      json: {
        type: 'new_in_internal',
        contact: contact,
        action: 'create_in_hubspot'
      }
    };
  }
  
  // Check if internal contact is newer
  const internalUpdated = new Date(contact.updated_at);
  const hubspotUpdated = new Date(hubspotMatch.properties.lastmodifieddate);
  
  if (internalUpdated > hubspotUpdated) {
    return {
      json: {
        type: 'updated_in_internal',
        contact: contact,
        action: 'update_in_hubspot'
      }
    };
  }
  
  return null;
}).filter(item => item !== null);

// Combine all changes
return [...hubspotUpdates, ...internalUpdates];
`
      }
    },
    {
      name: "Route Updates",
      type: "n8n-nodes-base.switch",
      position: [700, 300],
      parameters: {
        rules: {
          conditions: [
            {
              value1: "={{$json.action}}",
              operation: "equal",
              value2: "update_in_hubspot"
            },
            {
              value1: "={{$json.action}}",
              operation: "equal",
              value2: "create_in_hubspot"
            },
            {
              value1: "={{$json.action}}",
              operation: "equal",
              value2: "update_in_internal"
            },
            {
              value1: "={{$json.action}}",
              operation: "equal",
              value2: "create_in_internal"
            }
          ]
        }
      }
    },
    {
      name: "Update HubSpot Contact",
      type: "n8n-nodes-base.hubspot",
      position: [900, 100],
      parameters: {
        resource: "contact",
        operation: "update",
        contactId: "={{$json.contact.hubspot_id || $json.hubspotId}}",
        additionalFields: {
          properties: {
            firstname: "={{$json.contact.first_name}}",
            lastname: "={{$json.contact.last_name}}",
            email: "={{$json.contact.email}}",
            phone: "={{$json.contact.phone}}",
            // Add additional fields as needed
          }
        }
      }
    },
    {
      name: "Create HubSpot Contact",
      type: "n8n-nodes-base.hubspot",
      position: [900, 250],
      parameters: {
        resource: "contact",
        operation: "create",
        additionalFields: {
          properties: {
            firstname: "={{$json.contact.first_name}}",
            lastname: "={{$json.contact.last_name}}",
            email: "={{$json.contact.email}}",
            phone: "={{$json.contact.phone}}",
            // Add additional fields as needed
          }
        }
      }
    },
    {
      name: "Update Internal Contact",
      type: "n8n-nodes-base.function",
      position: [900, 400],
      parameters: {
        functionCode: `
// This function would update contacts in your internal database
// In reality, you might use another n8n node like a database connector
const contact = $input.first().json.contact;

// Process the HubSpot contact into internal format
const internalFormat = {
  first_name: contact.properties.firstname,
  last_name: contact.properties.lastname,
  email: contact.properties.email,
  phone: contact.properties.phone,
  hubspot_id: contact.id,
  updated_at: new Date().toISOString()
};

// In a real implementation, you would update your database
// For this example, we just return what would be updated
return [{
  json: {
    action: "updated_internal_contact",
    contact: internalFormat
  }
}];
`
      }
    },
    {
      name: "Create Internal Contact",
      type: "n8n-nodes-base.function",
      position: [900, 550],
      parameters: {
        functionCode: `
// This function would create new contacts in your internal database
// In reality, you might use another n8n node like a database connector
const contact = $input.first().json.contact;

// Process the HubSpot contact into internal format
const internalFormat = {
  first_name: contact.properties.firstname,
  last_name: contact.properties.lastname,
  email: contact.properties.email,
  phone: contact.properties.phone,
  hubspot_id: contact.id,
  created_at: new Date().toISOString(),
  updated_at: new Date().toISOString()
};

// In a real implementation, you would insert into your database
// For this example, we just return what would be created
return [{
  json: {
    action: "created_internal_contact",
    contact: internalFormat
  }
}];
`
      }
    },
    {
      name: "Sync Report",
      type: "n8n-nodes-base.function",
      position: [1100, 300],
      parameters: {
        functionCode: `
// Collect all results from previous nodes
const updateHubspot = $input.itemsFrom('Update HubSpot Contact');
const createHubspot = $input.itemsFrom('Create HubSpot Contact');
const updateInternal = $input.itemsFrom('Update Internal Contact');
const createInternal = $input.itemsFrom('Create Internal Contact');

// Generate sync summary
return [{
  json: {
    summary: "Sync Complete",
    timestamp: new Date().toISOString(),
    stats: {
      hubspot_updated: updateHubspot.length,
      hubspot_created: createHubspot.length,
      internal_updated: updateInternal.length,
      internal_created: createInternal.length,
      total_changes: updateHubspot.length + createHubspot.length + 
                    updateInternal.length + createInternal.length
    }
  }
}];
`
      }
    }
  ],
  connections: {
    "Schedule Trigger": {
      main: [
        [
          {
            node: "Get HubSpot Contacts",
            type: "main",
            index: 0
          },
          {
            node: "Get n8n Contacts",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "Get HubSpot Contacts": {
      main: [
        [
          {
            node: "Compare & Identify Changes",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "Get n8n Contacts": {
      main: [
        [
          {
            node: "Compare & Identify Changes",
            type: "main",
            index: 1
          }
        ]
      ]
    },
    "Compare & Identify Changes": {
      main: [
        [
          {
            node: "Route Updates",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "Route Updates": {
      main: [
        [
          {
            node: "Update HubSpot Contact",
            type: "main",
            index: 0
          }
        ],
        [
          {
            node: "Create HubSpot Contact",
            type: "main",
            index: 0
          }
        ],
        [
          {
            node: "Update Internal Contact",
            type: "main",
            index: 0
          }
        ],
        [
          {
            node: "Create Internal Contact",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "Update HubSpot Contact": {
      main: [
        [
          {
            node: "Sync Report",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "Create HubSpot Contact": {
      main: [
        [
          {
            node: "Sync Report",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "Update Internal Contact": {
      main: [
        [
          {
            node: "Sync Report",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "Create Internal Contact": {
      main: [
        [
          {
            node: "Sync Report",
            type: "main",
            index: 0
          }
        ]
      ]
    }
  }
});

return `
# HubSpot Two-Way Sync Workflow Created

I've created a new workflow that will synchronize contacts between HubSpot and your internal system.

**Workflow ID:** ${syncWorkflow.id}

## Configuration Needed:
1. The workflow is currently **inactive** - activate it once configured
2. ${credentialsMessage}
3. You'll need to customize the "Get n8n Contacts", "Update Internal Contact", and "Create Internal Contact" nodes to work with your specific database or storage system

## How It Works:
- Runs every 2 hours (configurable)
- Retrieves contacts from both HubSpot and your internal system
- Compares the data to identify new or updated contacts in either system
- Creates or updates contacts to keep both systems in sync
- Generates a sync report with statistics

## Customization:
You may need to modify the field mappings in the function nodes to match your specific data structure
and add any additional fields that need to be synchronized between the systems.
`;
```

### Slack Integration

#### Example: Creating a Workflow to Send n8n Notifications to Slack

```javascript
// User request: "Create a workflow that sends n8n execution notifications to Slack"

// Create the notification workflow
const notificationWorkflow = await useMcpTool('n8n-mcp-server', 'workflow_create', {
  name: "n8n Execution Notifications to Slack",
  active: false,
  nodes: [
    {
      name: "Webhook",
      type: "n8n-nodes-base.webhook",
      position: [100, 300],
      parameters: {
        path: "n8n-notification",
        responseMode: "onReceived",
        options: {
          responseData: "noData"
        }
      }
    },
    {
      name: "Format Slack Message",
      type: "n8n-nodes-base.function",
      position: [300, 300],
      parameters: {
        functionCode: `
// Parse webhook data from n8n
const data = $input.first().json;
const workflow = data.workflow;
const execution = data.execution;

// Determine status color
let color = "#36a64f"; // Green for success
let icon = "✅";

if (execution.status === "error") {
  color = "#ff0000"; // Red for error
  icon = "❌";
} else if (execution.status === "warning") {
  color = "#ffcc00"; // Yellow for warning
  icon = "⚠️";
}

// Create Slack message blocks
const blocks = [
  {
    "type": "header",
    "text": {
      "type": "plain_text",
      "text": \`\${icon} Workflow \${execution.status === "success" ? "Succeeded" : "Failed"}: \${workflow.name}\`,
      "emoji": true
    }
  },
  {
    "type": "divider"
  },
  {
    "type": "section",
    "fields": [
      {
        "type": "mrkdwn",
        "text": \`*Status:*\\n\${execution.status}\`
      },
      {
        "type": "mrkdwn",
        "text": \`*Execution Time:*\\n\${new Date(execution.startedAt).toLocaleString()}\`
      },
      {
        "type": "mrkdwn",
        "text": \`*Duration:*\\n\${Math.round((new Date(execution.finishedAt) - new Date(execution.startedAt)) / 1000)} seconds\`
      },
      {
        "type": "mrkdwn",
        "text": \`*Mode:*\\n\${execution.mode}\`
      }
    ]
  }
];

// Add error details if present
if (execution.status === "error" && execution.error) {
  blocks.push(
    {
      "type": "divider"
    },
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "*Error Details:*"
      }
    },
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": \`\\\`\\\`\${execution.error.message || "Unknown error"}\\\`\\\`\`
      }
    }
  );
}

// Add link to n8n execution
blocks.push(
  {
    "type": "divider"
  },
  {
    "type": "section",
    "text": {
      "type": "mrkdwn",
      "text": \`<\${data.n8nUrl}/workflow/\${workflow.id}|View Workflow> | <\${data.n8nUrl}/execution/\${execution.id}|View Execution>\`
    }
  }
);

return [{
  json: {
    blocks: blocks,
    text: \`\${icon} Workflow \${execution.status === "success" ? "Succeeded" : "Failed"}: \${workflow.name}\`,
    channel: "#n8n-notifications"
  }
}];
`
      }
    },
    {
      name: "Send to Slack",
      type: "n8n-nodes-base.slack",
      position: [500, 300],
      parameters: {
        token: "{{$env.SLACK_TOKEN}}",
        text: "={{$json.text}}",
        channel: "={{$json.channel}}",
        otherOptions: {
          blocks: "={{$json.blocks}}"
        }
      }
    }
  ],
  connections: {
    "Webhook": {
      main: [
        [
          {
            node: "Format Slack Message",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "Format Slack Message": {
      main: [
        [
          {
            node: "Send to Slack",
            type: "main",
            index: 0
          }
        ]
      ]
    }
  }
});

// Create workflow to set up webhook in n8n settings
const setupWorkflow = await useMcpTool('n8n-mcp-server', 'workflow_create', {
  name: "Configure n8n Execution Notifications",
  active: false,
  nodes: [
    {
      name: "Manual Trigger",
      type: "n8n-nodes-base.manualTrigger",
      position: [100, 300],
      parameters: {}
    },
    {
      name: "n8n Settings",
      type: "n8n-nodes-base.n8n",
      position: [300, 300],
      parameters: {
        resource: "settings",
        operation: "update",
        values: {
          executionTimeout: 120,
          saveManualExecutions: true,
          saveDataErrorExecution: "all", 
          saveDataSuccessExecution: "all",
          saveExecutionProgress: true,
          executeTimeout: 120,
          maxExecutionTimeout: 300,
          workflowCallerIdsAllowed: [],
          timezone: "UTC",
          executionMode: "regular",
          pushBackend: "websocket",
          webhookDebugEnabled: false,
          webhookUrl: "={{$json.webhookUrl}}",
          logLevel: "info"
        }
      }
    },
    {
      name: "Create Instructions",
      type: "n8n-nodes-base.function",
      position: [500, 300],
      parameters: {
        functionCode: `
const webhookNode = $input.itemsFrom('Webhook')? $input.itemsFrom('Webhook')[0] : { json: { webhookUrl: "unknown" } };
const webhookUrl = webhookNode.json.webhookUrl;

return [{
  json: {
    instructions: \`
# n8n to Slack Notification Setup

I've created two workflows:

1. **n8n Execution Notifications to Slack** - This workflow receives execution notifications from n8n and sends them to Slack
   - ID: ${notificationWorkflow.id}
   - Webhook URL: \${webhookUrl}

2. **Configure n8n Execution Notifications** - This workflow configures n8n settings to send notifications
   - ID: ${setupWorkflow.id}

## Next Steps:

1. Create a Slack app and obtain a token with the following permissions:
   - chat:write
   - chat:write.public
   
2. Set the environment variable \`SLACK_TOKEN\` with your Slack token

3. Run the "Configure n8n Execution Notifications" workflow after setting the \`webhookUrl\` parameter to:
   \${webhookUrl}

4. Activate the "n8n Execution Notifications to Slack" workflow

5. Customize the channel in the "Format Slack Message" node if needed (default is #n8n-notifications)

Once completed, you'll receive Slack notifications whenever a workflow execution succeeds or fails!
\`
  }
}];
`
      }
    }
  ],
  connections: {
    "Manual Trigger": {
      main: [
        [
          {
            node: "n8n Settings",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "n8n Settings": {
      main: [
        [
          {
            node: "Create Instructions",
            type: "main",
            index: 0
          }
        ]
      ]
    }
  }
});

// Execute the workflow to get the webhook URL
const execution = await useMcpTool('n8n-mcp-server', 'execution_run', {
  workflowId: notificationWorkflow.id,
  waitForCompletion: true
});

// Extract webhook URL from the execution
let webhookUrl = "undefined";
if (execution.status === "success" && execution.data?.resultData?.runData?.Webhook?.[0]?.data?.webhookUrl) {
  webhookUrl = execution.data.resultData.runData.Webhook[0].data.webhookUrl;
}

return `
# n8n to Slack Notification Setup

I've created two workflows:

1. **n8n Execution Notifications to Slack** - This workflow receives execution notifications from n8n and sends them to Slack
   - ID: ${notificationWorkflow.id}
   - Webhook URL: ${webhookUrl}

2. **Configure n8n Execution Notifications** - This workflow configures n8n settings to send notifications
   - ID: ${setupWorkflow.id}

## Next Steps:

1. Create a Slack app and obtain a token with the following permissions:
   - chat:write
   - chat:write.public
   
2. Set the environment variable \`SLACK_TOKEN\` with your Slack token

3. Run the "Configure n8n Execution Notifications" workflow after setting the \`webhookUrl\` parameter to:
   ${webhookUrl}

4. Activate the "n8n Execution Notifications to Slack" workflow

5. Customize the channel in the "Format Slack Message" node if needed (default is #n8n-notifications)

Once completed, you'll receive Slack notifications whenever a workflow execution completes!
```
# Integration Examples

This page provides examples of integrating the n8n MCP Server with other systems and AI assistant platforms.

## AI Assistant Integration Examples

### Claude AI Assistant Integration

#### Example: Setting Up n8n MCP Server with Claude

```javascript
// Register the n8n MCP Server with Claude using the MCP Installer
const installationResult = await useMcpTool('mcp-installer', 'install_repo_mcp_server', {
  name: 'n8n-mcp-server',
  env: [
    "N8N_API_URL=http://localhost:5678/api/v1",
    "N8N_API_KEY=your_n8n_api_key_here",
    "DEBUG=false"
  ]
});

// Once registered, Claude can interact with n8n
// Here's an example conversation:

// User: "Show me my active workflows in n8n"
// Claude: (uses workflow_list tool to retrieve and display active workflows)

// User: "Execute my 'Daily Report' workflow"
// Claude: (uses execution_run tool to start the workflow and provide status)
```

#### Example: Using n8n to Extend Claude's Capabilities

```javascript
// Using n8n as a bridge to external systems 
// This example shows how Claude can access a database through n8n

// User: "Show me the top 5 customers from my database"
// Claude would:

// 1. Find the appropriate workflow
const workflows = await useMcpTool('n8n-mcp-server', 'workflow_list', {});
const dbQueryWorkflow = workflows.find(w => 
  w.name.toLowerCase().includes('database query') || 
  w.name.toLowerCase().includes('db query')
);

if (!dbQueryWorkflow) {
  return "I couldn't find a database query workflow. Would you like me to help you create one?";
}

// 2. Execute the workflow with the appropriate parameters
const execution = await useMcpTool('n8n-mcp-server', 'execution_run', {
  workflowId: dbQueryWorkflow.id,
  data: {
    query: "SELECT * FROM customers ORDER BY total_purchases DESC LIMIT 5",
    format: "table"
  },
  waitForCompletion: true
});

// 3. Present the results to the user
if (execution.status !== "success") {
  return "There was an error querying the database. Error: " + (execution.error || "Unknown error");
}

// Format the results as a table
const customers = execution.data.resultData.runData.lastNode[0].data.json;
let response = "# Top 5 Customers\n\n";
response += "| Customer Name | Email | Total Purchases |\n";
response += "|--------------|-------|----------------|\n";

customers.forEach(customer => {
  response += `| ${customer.name} | ${customer.email} | $${customer.total_purchases.toFixed(2)} |\n`;
});

return response;
```

### OpenAI Assistant Integration

#### Example: Connecting n8n MCP Server to OpenAI Assistant

```javascript
// This is a conceptual example of how an OpenAI Assistant might interact with the n8n MCP Server

// In an OpenAI Assistant Function definition:
{
  "name": "n8n_workflow_list",
  "description": "List all workflows in n8n",
  "parameters": {
    "type": "object",
    "properties": {
      "active": {
        "type": "boolean",
        "description": "Filter by active status (optional)"
      }
    }
  }
}

// The function would call the n8n MCP Server:
async function n8n_workflow_list(params) {
  // Call the n8n MCP Server API
  const response = await fetch('http://localhost:3000/n8n-mcp/tools/workflow_list', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer YOUR_AUTH_TOKEN'
    },
    body: JSON.stringify(params)
  });
  
  return await response.json();
}

// The OpenAI Assistant would then use this function when asked about n8n workflows
```

## Integration with External Systems

### n8n to Git Integration

#### Example: Using n8n MCP Server to Manage Workflow Versioning in Git

```javascript
// User request: "Backup all my workflows to my Git repository"

// The assistant first creates a workflow for Git backup
const backupWorkflow = await useMcpTool('n8n-mcp-server', 'workflow_create', {
  name: "Workflow Git Backup",
  active: false,
  nodes: [
    {
      name: "Manual Trigger",
      type: "n8n-nodes-base.manualTrigger",
      position: [100, 300],
      parameters: {}
    },
    {
      name: "Get All Workflows",
      type: "n8n-nodes-base.n8n",
      position: [300, 300],
      parameters: {
        resource: "workflow",
        operation: "getAll"
      }
    },
    {
      name: "Format For Git",
      type: "n8n-nodes-base.function",
      position: [500, 300],
      parameters: {
        functionCode: `
// Convert workflows to individual JSON files
const workflows = items;
const outputItems = [];

for (const workflow of workflows) {
  // Create a sanitized filename
  const filename = workflow.json.name
    .replace(/[^a-zA-Z0-9]/g, '_')
    .toLowerCase() + '.json';
  
  outputItems.push({
    json: {
      filename: filename,
      content: JSON.stringify(workflow.json, null, 2),
      commit_message: \`Backup workflow: \${workflow.json.name}\`
    }
  });
}

return outputItems;
`
      }
    },
    {
      name: "Git Commit",
      type: "n8n-nodes-base.git",
      position: [700, 300],
      parameters: {
        repository: "{{$env.GIT_REPO_PATH}}",
        directory: "workflows",
        operation: "commit",
        message: "Automated workflow backup {{$now.format('YYYY-MM-DD HH:mm')}}",
        filePath: "={{$json.filename}}",
        fileContent: "={{$json.content}}"
      }
    },
    {
      name: "Git Push",
      type: "n8n-nodes-base.git",
      position: [900, 300],
      parameters: {
        repository: "{{$env.GIT_REPO_PATH}}",
        operation: "push"
      }
    }
  ],
  connections: {
    "Manual Trigger": {
      main: [
        [
          {
            node: "Get All Workflows",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "Get All Workflows": {
      main: [
        [
          {
            node: "Format For Git",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "Format For Git": {
      main: [
        [
          {
            node: "Git Commit",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "Git Commit": {
      main: [
        [
          {
            node: "Git Push",
            type: "main",
            index: 0
          }
        ]
      ]
    }
  }
});

return `
# Git Backup Workflow Created

I've created a new workflow that will backup all your n8n workflows to Git.

**Workflow ID:** ${backupWorkflow.id}

## Configuration Needed:
1. The workflow is currently **inactive** - activate it once configured
2. Set the environment variable \`GIT_REPO_PATH\` to point to your Git repository
3. Ensure the Git repository is properly configured with credentials

## How It Works:
- When executed, this workflow retrieves all n8n workflows
- Converts each workflow to a JSON file
- Commits each file to the specified Git repository
- Pushes the changes to the remote repository

## Usage:
You can run this workflow manually whenever you want to backup your workflows,
or add a Schedule Trigger to automate regular backups.
`;
```

### CRM Integration

#### Example: Setting Up a Two-Way Sync Between n8n and a CRM

```javascript
// User request: "Help me set up a sync between n8n and my HubSpot CRM"

// First check for existing credentials
const workflows = await useMcpTool('n8n-mcp-server', 'workflow_list', {});
const hubspotWorkflows = workflows.filter(w => 
  w.name.toLowerCase().includes('hubspot')
);

let credentialsMessage = "";
if (hubspotWorkflows.length > 0) {
  credentialsMessage = "I noticed you already have HubSpot workflows. I'll assume you have HubSpot credentials set up.";
} else {
  credentialsMessage = "You'll need to set up HubSpot credentials in n8n before activating this workflow.";
}

// Create the sync workflow
const syncWorkflow = await useMcpTool('n8n-mcp-server', 'workflow_create', {
  name: "HubSpot Two-Way Sync",
  active: false,
  nodes: [
    {
      name: "Schedule Trigger",
      type: "n8n-nodes-base.scheduleTrigger",
      position: [100, 300],
      parameters: {
        cronExpression: "0 */2 * * *" // Every 2 hours
      }
    },
    {
      name: "Get HubSpot Contacts",
      type: "n8n-nodes-base.hubspot",
      position: [300, 200],
      parameters: {
        resource: "contact",
        operation: "getAll",
        returnAll: true,
        additionalFields: {
          formattedDate: true
        }
      }
    },
    {
      name: "Get n8n Contacts",
      type: "n8n-nodes-base.function",
      position: [300, 400],
      parameters: {
        functionCode: `
// This function would retrieve contacts from your internal database
// For example purposes, we're using a placeholder
// In reality, you might use another n8n node to fetch from your database

return [
  {
    json: {
      internalContacts: [
        // Example internal contacts
        // In a real scenario, these would come from your database
      ]
    }
  }
];
`
      }
    },
    {
      name: "Compare & Identify Changes",
      type: "n8n-nodes-base.function",
      position: [500, 300],
      parameters: {
        functionCode: `
const hubspotContacts = $input.first();
const internalContacts = $input.last().json.internalContacts;

// Identify new/updated contacts in HubSpot
const hubspotUpdates = hubspotContacts.json.map(contact => {
  // Find matching internal contact by email
  const internalMatch = internalContacts.find(
    ic => ic.email === contact.properties.email
  );
  
  if (!internalMatch) {
    return {
      json: {
        type: 'new_in_hubspot',
        contact: contact,
        action: 'create_in_internal'
      }
    };
  }
  
  // Check if HubSpot contact is newer
  const hubspotUpdated = new Date(contact.properties.lastmodifieddate);
  const internalUpdated = new Date(internalMatch.updated_at);
  
  if (hubspotUpdated > internalUpdated) {
    return {
      json: {
        type: 'updated_in_hubspot',
        contact: contact,
        action: 'update_in_internal'
      }
    };
  }
  
  return null;
}).filter(item => item !== null);

// Identify new/updated contacts in internal system
const internalUpdates = internalContacts.map(contact => {
  // Find matching HubSpot contact by email
  const hubspotMatch = hubspotContacts.json.find(
    hc => hc.properties.email === contact.email
  );
  
  if (!hubspotMatch) {
    return {
      json: {
        type: 'new_in_internal',
        contact: contact,
        action: 'create_in_hubspot'
      }
    };
  }
  
  // Check if internal contact is newer
  const internalUpdated = new Date(contact.updated_at);
  const hubspotUpdated = new Date(hubspotMatch.properties.lastmodifieddate);
  
  if (internalUpdated > hubspotUpdated) {
    return {
      json: {
        type: 'updated_in_internal',
        contact: contact,
        action: 'update_in_hubspot'
      }
    };
  }
  
  return null;
}).filter(item => item !== null);

// Combine all changes
return [...hubspotUpdates, ...internalUpdates];
`
      }
    },
    {
      name: "Route Updates",
      type: "n8n-nodes-base.switch",
      position: [700, 300],
      parameters: {
        rules: {
          conditions: [
            {
              value1: "={{$json.action}}",
              operation: "equal",
              value2: "update_in_hubspot"
            },
            {
              value1: "={{$json.action}}",
              operation: "equal",
              value2: "create_in_hubspot"
            },
            {
              value1: "={{$json.action}}",
              operation: "equal",
              value2: "update_in_internal"
            },
            {
              value1: "={{$json.action}}",
              operation: "equal",
              value2: "create_in_internal"
            }
          ]
        }
      }
    },
    {
      name: "Update HubSpot Contact",
      type: "n8n-nodes-base.hubspot",
      position: [900, 100],
      parameters: {
        resource: "contact",
        operation: "update",
        contactId: "={{$json.contact.hubspot_id || $json.hubspotId}}",
        additionalFields: {
          properties: {
            firstname: "={{$json.contact.first_name}}",
            lastname: "={{$json.contact.last_name}}",
            email: "={{$json.contact.email}}",
            phone: "={{$json.contact.phone}}",
            // Add additional fields as needed
          }
        }
      }
    },
    {
      name: "Create HubSpot Contact",
      type: "n8n-nodes-base.hubspot",
      position: [900, 250],
      parameters: {
        resource: "contact",
        operation: "create",
        additionalFields: {
          properties: {
            firstname: "={{$json.contact.first_name}}",
            lastname: "={{$json.contact.last_name}}",
            email: "={{$json.contact.email}}",
            phone: "={{$json.contact.phone}}",
            // Add additional fields as needed
          }
        }
      }
    },
    {
      name: "Update Internal Contact",
      type: "n8n-nodes-base.function",
      position: [900, 400],
      parameters: {
        functionCode: `
// This function would update contacts in your internal database
// In reality, you might use another n8n node like a database connector
const contact = $input.first().json.contact;

// Process the HubSpot contact into internal format
const internalFormat = {
  first_name: contact.properties.firstname,
  last_name: contact.properties.lastname,
  email: contact.properties.email,
  phone: contact.properties.phone,
  hubspot_id: contact.id,
  updated_at: new Date().toISOString()
};

// In a real implementation, you would update your database
// For this example, we just return what would be updated
return [{
  json: {
    action: "updated_internal_contact",
    contact: internalFormat
  }
}];
`
      }
    },
    {
      name: "Create Internal Contact",
      type: "n8n-nodes-base.function",
      position: [900, 550],
      parameters: {
        functionCode: `
// This function would create new contacts in your internal database
// In reality, you might use another n8n node like a database connector
const contact = $input.first().json.contact;

// Process the HubSpot contact into internal format
const internalFormat = {
  first_name: contact.properties.firstname,
  last_name: contact.properties.lastname,
  email: contact.properties.email,
  phone: contact.properties.phone,
  hubspot_id: contact.id,
  created_at: new Date().toISOString(),
  updated_at: new Date().toISOString()
};

// In a real implementation, you would insert into your database
// For this example, we just return what would be created
return [{
  json: {
    action: "created_internal_contact",
    contact: internalFormat
  }
}];
`
      }
    },
    {
      name: "Sync Report",
      type: "n8n-nodes-base.function",
      position: [1100, 300],
      parameters: {
        functionCode: `
// Collect all results from previous nodes
const updateHubspot = $input.itemsFrom('Update HubSpot Contact');
const createHubspot = $input.itemsFrom('Create HubSpot Contact');
const updateInternal = $input.itemsFrom('Update Internal Contact');
const createInternal = $input.itemsFrom('Create Internal Contact');

// Generate sync summary
return [{
  json: {
    summary: "Sync Complete",
    timestamp: new Date().toISOString(),
    stats: {
      hubspot_updated: updateHubspot.length,
      hubspot_created: createHubspot.length,
      internal_updated: updateInternal.length,
      internal_created: createInternal.length,
      total_changes: updateHubspot.length + createHubspot.length + 
                    updateInternal.length + createInternal.length
    }
  }
}];
`
      }
    }
  ],
  connections: {
    "Schedule Trigger": {
      main: [
        [
          {
            node: "Get HubSpot Contacts",
            type: "main",
            index: 0
          },
          {
            node: "Get n8n Contacts",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "Get HubSpot Contacts": {
      main: [
        [
          {
            node: "Compare & Identify Changes",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "Get n8n Contacts": {
      main: [
        [
          {
            node: "Compare & Identify Changes",
            type: "main",
            index: 1
          }
        ]
      ]
    },
    "Compare & Identify Changes": {
      main: [
        [
          {
            node: "Route Updates",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "Route Updates": {
      main: [
        [
          {
            node: "Update HubSpot Contact",
            type: "main",
            index: 0
          }
        ],
        [
          {
            node: "Create HubSpot Contact",
            type: "main",
            index: 0
          }
        ],
        [
          {
            node: "Update Internal Contact",
            type: "main",
            index: 0
          }
        ],
        [
          {
            node: "Create Internal Contact",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "Update HubSpot Contact": {
      main: [
        [
          {
            node: "Sync Report",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "Create HubSpot Contact": {
      main: [
        [
          {
            node: "Sync Report",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "Update Internal Contact": {
      main: [
        [
          {
            node: "Sync Report",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "Create Internal Contact": {
      main: [
        [
          {
            node: "Sync Report",
            type: "main",
            index: 0
          }
        ]
      ]
    }
  }
});

return `
# HubSpot Two-Way Sync Workflow Created

I've created a new workflow that will synchronize contacts between HubSpot and your internal system.

**Workflow ID:** ${syncWorkflow.id}

## Configuration Needed:
1. The workflow is currently **inactive** - activate it once configured
2. ${credentialsMessage}
3. You'll need to customize the "Get n8n Contacts", "Update Internal Contact", and "Create Internal Contact" nodes to work with your specific database or storage system

## How It Works:
- Runs every 2 hours (configurable)
- Retrieves contacts from both HubSpot and your internal system
- Compares the data to identify new or updated contacts in either system
- Creates or updates contacts to keep both systems in sync
- Generates a sync report with statistics

## Customization:
You may need to modify the field mappings in the function nodes to match your specific data structure
and add any additional fields that need to be synchronized between the systems.
`;
```

### Slack Integration

#### Example: Creating a Workflow to Send n8n Notifications to Slack

```javascript
// User request: "Create a workflow that sends n8n execution notifications to Slack"

// Create the notification workflow
const notificationWorkflow = await useMcpTool('n8n-mcp-server', 'workflow_create', {
  name: "n8n Execution Notifications to Slack",
  active: false,
  nodes: [
    {
      name: "Webhook",
      type: "n8n-nodes-base.webhook",
      position: [100, 300],
      parameters: {
        path: "n8n-notification",
        responseMode: "onReceived",
        options: {
          responseData: "noData"
        }
      }
    },
    {
      name: "Format Slack Message",
      type: "n8n-nodes-base.function",
      position: [300, 300],
      parameters: {
        functionCode: `
// Parse webhook data from n8n
const data = $input.first().json;
const workflow = data.workflow;
const execution = data.execution;

// Determine status color
let color = "#36a64f"; // Green for success
let icon = "✅";

if (execution.status === "error") {
  color = "#ff0000"; // Red for error
  icon = "❌";
} else if (execution.status === "warning") {
  color = "#ffcc00"; // Yellow for warning
  icon = "⚠️";
}

// Create Slack message blocks
const blocks = [
  {
    "type": "header",
    "text": {
      "type": "plain_text",
      "text": \`\${icon} Workflow \${execution.status === "success" ? "Succeeded" : "Failed"}: \${workflow.name}\`,
      "emoji": true
    }
  },
  {
    "type": "divider"
  },
  {
    "type": "section",
    "fields": [
      {
        "type": "mrkdwn",
        "text": \`*Status:*\\n\${execution.status}\`
      },
      {
        "type": "mrkdwn",
        "text": \`*Execution Time:*\\n\${new Date(execution.startedAt).toLocaleString()}\`
      },
      {
        "type": "mrkdwn",
        "text": \`*Duration:*\\n\${Math.round((new Date(execution.finishedAt) - new Date(execution.startedAt)) / 1000)} seconds\`
      },
      {
        "type": "mrkdwn",
        "text": \`*Mode:*\\n\${execution.mode}\`
      }
    ]
  }
];

// Add error details if present
if (execution.status === "error" && execution.error) {
  blocks.push(
    {
      "type": "divider"
    },
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "*Error Details:*"
      }
    },
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": \`\\\`\\\`\${execution.error.message || "Unknown error"}\\\`\\\`\`
      }
    }
  );
}

// Add link to n8n execution
blocks.push(
  {
    "type": "divider"
  },
  {
    "type": "section",
    "text": {
      "type": "mrkdwn",
      "text": \`<\${data.n8nUrl}/workflow/\${workflow.id}|View Workflow> | <\${data.n8nUrl}/execution/\${execution.id}|View Execution>\`
    }
  }
);

return [{
  json: {
    blocks: blocks,
    text: \`\${icon} Workflow \${execution.status === "success" ? "Succeeded" : "Failed"}: \${workflow.name}\`,
    channel: "#n8n-notifications"
  }
}];
`
      }
    },
    {
      name: "Send to Slack",
      type: "n8n-nodes-base.slack",
      position: [500, 300],
      parameters: {
        token: "{{$env.SLACK_TOKEN}}",
        text: "={{$json.text}}",
        channel: "={{$json.channel}}",
        otherOptions: {
          blocks: "={{$json.blocks}}"
        }
      }
    }
  ],
  connections: {
    "Webhook": {
      main: [
        [
          {
            node: "Format Slack Message",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "Format Slack Message": {
      main: [
        [
          {
            node: "Send to Slack",
            type: "main",
            index: 0
          }
        ]
      ]
    }
  }
});

// Create workflow to set up webhook in n8n settings
const setupWorkflow = await useMcpTool('n8n-mcp-server', 'workflow_create', {
  name: "Configure n8n Execution Notifications",
  active: false,
  nodes: [
    {
      name: "Manual Trigger",
      type: "n8n-nodes-base.manualTrigger",
      position: [100, 300],
      parameters: {}
    },
    {
      name: "n8n Settings",
      type: "n8n-nodes-base.n8n",
      position: [300, 300],
      parameters: {
        resource: "settings",
        operation: "update",
        values: {
          executionTimeout: 120,
          saveManualExecutions: true,
          saveDataErrorExecution: "all", 
          saveDataSuccessExecution: "all",
          saveExecutionProgress: true,
          executeTimeout: 120,
          maxExecutionTimeout: 300,
          workflowCallerIdsAllowed: [],
          timezone: "UTC",
          executionMode: "regular",
          pushBackend: "websocket",
          webhookDebugEnabled: false,
          webhookUrl: "={{$json.webhookUrl}}",
          logLevel: "info"
        }
      }
    },
    {
      name: "Create Instructions",
      type: "n8n-nodes-base.function",
      position: [500, 300],
      parameters: {
        functionCode: `
const webhookNode = $input.itemsFrom('Webhook')? $input.itemsFrom('Webhook')[0] : { json: { webhookUrl: "unknown" } };
const webhookUrl = webhookNode.json.webhookUrl;

return [{
  json: {
    instructions: \`
# n8n to Slack Notification Setup

I've created two workflows:

1. **n8n Execution Notifications to Slack** - This workflow receives execution notifications from n8n and sends them to Slack
   - ID: ${notificationWorkflow.id}
   - Webhook URL: \${webhookUrl}

2. **Configure n8n Execution Notifications** - This workflow configures n8n settings to send notifications
   - ID: ${setupWorkflow.id}

## Next Steps:

1. Create a Slack app and obtain a token with the following permissions:
   - chat:write
   - chat:write.public
   
2. Set the environment variable \`SLACK_TOKEN\` with your Slack token

3. Run the "Configure n8n Execution Notifications" workflow after setting the \`webhookUrl\` parameter to:
   \${webhookUrl}

4. Activate the "n8n Execution Notifications to Slack" workflow

5. Customize the channel in the "Format Slack Message" node if needed (default is #n8n-notifications)

Once completed, you'll receive Slack notifications whenever a workflow execution succeeds or fails!
\`
  }
}];
`
      }
    }
  ],
  connections: {
    "Manual Trigger": {
      main: [
        [
          {
            node: "n8n Settings",
            type: "main",
            index: 0
          }
        ]
      ]
    },
    "n8n Settings": {
      main: [
        [
          {
            node: "Create Instructions",
            type: "main",
            index: 0
          }
        ]
      ]
    }
  }
});

// Execute the workflow to get the webhook URL
const execution = await useMcpTool('n8n-mcp-server', 'execution_run', {
  workflowId: notificationWorkflow.id,
  waitForCompletion: true
});

// Extract webhook URL from the execution
let webhookUrl = "undefined";
if (execution.status === "success" && execution.data?.resultData?.runData?.Webhook?.[0]?.data?.webhookUrl) {
  webhookUrl = execution.data.resultData.runData.Webhook[0].data.webhookUrl;
}

return `
# n8n to Slack Notification Setup

I've created two workflows:

1. **n8n Execution Notifications to Slack** - This workflow receives execution notifications from n8n and sends them to Slack
   - ID: ${notificationWorkflow.id}
   - Webhook URL: ${webhookUrl}

2. **Configure n8n Execution Notifications** - This workflow configures n8n settings to send notifications
   - ID: ${setupWorkflow.id}

## Next Steps:

1. Create a Slack app and obtain a token with the following permissions:
   - chat:write
   - chat:write.public
   
2. Set the environment variable \`SLACK_TOKEN\` with your Slack token

3. Run the "Configure n8n Execution Notifications" workflow after setting the \`webhookUrl\` parameter to:
   ${webhookUrl}

4. Activate the "n8n Execution Notifications to Slack" workflow

5. Customize the channel in the "Format Slack Message" node if needed (default is #n8n-notifications)

Once completed, you

```

--------------------------------------------------------------------------------
/n8n-openapi.yml:
--------------------------------------------------------------------------------

```yaml
openapi: 3.0.0
info:
  title: n8n Public API
  description: n8n Public API
  termsOfService: https://n8n.io/legal/terms
  contact:
    email: [email protected]
  license:
    name: Sustainable Use License
    url: https://github.com/n8n-io/n8n/blob/master/LICENSE.md
  version: 1.1.1
servers:
  - url: /api/v1
security:
  - ApiKeyAuth: []
tags:
  - name: User
    description: Operations about users
  - name: Audit
    description: Operations about security audit
  - name: Execution
    description: Operations about executions
  - name: Workflow
    description: Operations about workflows
  - name: Credential
    description: Operations about credentials
  - name: Tags
    description: Operations about tags
  - name: SourceControl
    description: Operations about source control
  - name: Variables
    description: Operations about variables
  - name: Projects
    description: Operations about projects
externalDocs:
  description: n8n API documentation
  url: https://docs.n8n.io/api/
paths:
  /audit:
    post:
      x-eov-operation-id: generateAudit
      x-eov-operation-handler: v1/handlers/audit/audit.handler
      tags:
        - Audit
      summary: Generate an audit
      description: Generate a security audit for your n8n instance.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                additionalOptions:
                  type: object
                  properties:
                    daysAbandonedWorkflow:
                      type: integer
                      description: Days for a workflow to be considered abandoned if not executed
                    categories:
                      type: array
                      items:
                        type: string
                        enum:
                          - credentials
                          - database
                          - nodes
                          - filesystem
                          - instance
      responses:
        '200':
          description: Operation successful.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/audit'
        '401':
          $ref: '#/components/responses/unauthorized'
        '500':
          description: Internal server error.
  /credentials:
    post:
      x-eov-operation-id: createCredential
      x-eov-operation-handler: v1/handlers/credentials/credentials.handler
      tags:
        - Credential
      summary: Create a credential
      description: Creates a credential that can be used by nodes of the specified type.
      requestBody:
        description: Credential to be created.
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/credential'
      responses:
        '200':
          description: Operation successful.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/create-credential-response'
        '401':
          $ref: '#/components/responses/unauthorized'
        '415':
          description: Unsupported media type.
  /credentials/{id}:
    delete:
      x-eov-operation-id: deleteCredential
      x-eov-operation-handler: v1/handlers/credentials/credentials.handler
      tags:
        - Credential
      summary: Delete credential by ID
      description: Deletes a credential from your instance. You must be the owner of the credentials
      operationId: deleteCredential
      parameters:
        - name: id
          in: path
          description: The credential ID that needs to be deleted
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Operation successful.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/credential'
        '401':
          $ref: '#/components/responses/unauthorized'
        '404':
          $ref: '#/components/responses/notFound'
  /credentials/schema/{credentialTypeName}:
    get:
      x-eov-operation-id: getCredentialType
      x-eov-operation-handler: v1/handlers/credentials/credentials.handler
      tags:
        - Credential
      summary: Show credential data schema
      parameters:
        - name: credentialTypeName
          in: path
          description: The credential type name that you want to get the schema for
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Operation successful.
          content:
            application/json:
              schema:
                type: object
              examples:
                freshdeskApi:
                  value:
                    additionalProperties: false
                    type: object
                    properties:
                      apiKey:
                        type: string
                      domain:
                        type: string
                    required:
                      - apiKey
                      - domain
                slackOAuth2Api:
                  value:
                    additionalProperties: false
                    type: object
                    properties:
                      clientId:
                        type: string
                      clientSecret:
                        type: string
                    required:
                      - clientId
                      - clientSecret
        '401':
          $ref: '#/components/responses/unauthorized'
        '404':
          $ref: '#/components/responses/notFound'
  /executions:
    get:
      x-eov-operation-id: getExecutions
      x-eov-operation-handler: v1/handlers/executions/executions.handler
      tags:
        - Execution
      summary: Retrieve all executions
      description: Retrieve all executions from your instance.
      parameters:
        - $ref: '#/components/parameters/includeData'
        - name: status
          in: query
          description: Status to filter the executions by.
          required: false
          schema:
            type: string
            enum:
              - error
              - success
              - waiting
        - name: workflowId
          in: query
          description: Workflow to filter the executions by.
          required: false
          schema:
            type: string
            example: '1000'
        - name: projectId
          in: query
          required: false
          explode: false
          allowReserved: true
          schema:
            type: string
            example: VmwOO9HeTEj20kxM
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/cursor'
      responses:
        '200':
          description: Operation successful.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/executionList'
        '401':
          $ref: '#/components/responses/unauthorized'
        '404':
          $ref: '#/components/responses/notFound'
  /executions/{id}:
    get:
      x-eov-operation-id: getExecution
      x-eov-operation-handler: v1/handlers/executions/executions.handler
      tags:
        - Execution
      summary: Retrieve an execution
      description: Retrieve an execution from your instance.
      parameters:
        - $ref: '#/components/parameters/executionId'
        - $ref: '#/components/parameters/includeData'
      responses:
        '200':
          description: Operation successful.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/execution'
        '401':
          $ref: '#/components/responses/unauthorized'
        '404':
          $ref: '#/components/responses/notFound'
    delete:
      x-eov-operation-id: deleteExecution
      x-eov-operation-handler: v1/handlers/executions/executions.handler
      tags:
        - Execution
      summary: Delete an execution
      description: Deletes an execution from your instance.
      parameters:
        - $ref: '#/components/parameters/executionId'
      responses:
        '200':
          description: Operation successful.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/execution'
        '401':
          $ref: '#/components/responses/unauthorized'
        '404':
          $ref: '#/components/responses/notFound'
  /tags:
    post:
      x-eov-operation-id: createTag
      x-eov-operation-handler: v1/handlers/tags/tags.handler
      tags:
        - Tags
      summary: Create a tag
      description: Create a tag in your instance.
      requestBody:
        description: Created tag object.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/tag'
        required: true
      responses:
        '201':
          description: A tag object
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/tag'
        '400':
          $ref: '#/components/responses/badRequest'
        '401':
          $ref: '#/components/responses/unauthorized'
        '409':
          $ref: '#/components/responses/conflict'
    get:
      x-eov-operation-id: getTags
      x-eov-operation-handler: v1/handlers/tags/tags.handler
      tags:
        - Tags
      summary: Retrieve all tags
      description: Retrieve all tags from your instance.
      parameters:
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/cursor'
      responses:
        '200':
          description: Operation successful.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/tagList'
        '401':
          $ref: '#/components/responses/unauthorized'
  /tags/{id}:
    get:
      x-eov-operation-id: getTag
      x-eov-operation-handler: v1/handlers/tags/tags.handler
      tags:
        - Tags
      summary: Retrieves a tag
      description: Retrieves a tag.
      parameters:
        - $ref: '#/components/parameters/tagId'
      responses:
        '200':
          description: Operation successful.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/tag'
        '401':
          $ref: '#/components/responses/unauthorized'
        '404':
          $ref: '#/components/responses/notFound'
    delete:
      x-eov-operation-id: deleteTag
      x-eov-operation-handler: v1/handlers/tags/tags.handler
      tags:
        - Tags
      summary: Delete a tag
      description: Deletes a tag.
      parameters:
        - $ref: '#/components/parameters/tagId'
      responses:
        '200':
          description: Operation successful.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/tag'
        '401':
          $ref: '#/components/responses/unauthorized'
        '403':
          $ref: '#/components/responses/forbidden'
        '404':
          $ref: '#/components/responses/notFound'
    put:
      x-eov-operation-id: updateTag
      x-eov-operation-handler: v1/handlers/tags/tags.handler
      tags:
        - Tags
      summary: Update a tag
      description: Update a tag.
      parameters:
        - $ref: '#/components/parameters/tagId'
      requestBody:
        description: Updated tag object.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/tag'
        required: true
      responses:
        '200':
          description: Tag object
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/tag'
        '400':
          $ref: '#/components/responses/badRequest'
        '401':
          $ref: '#/components/responses/unauthorized'
        '404':
          $ref: '#/components/responses/notFound'
        '409':
          $ref: '#/components/responses/conflict'
  /workflows:
    post:
      x-eov-operation-id: createWorkflow
      x-eov-operation-handler: v1/handlers/workflows/workflows.handler
      tags:
        - Workflow
      summary: Create a workflow
      description: Create a workflow in your instance.
      requestBody:
        description: Created workflow object.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workflow'
        required: true
      responses:
        '200':
          description: A workflow object
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workflow'
        '400':
          $ref: '#/components/responses/badRequest'
        '401':
          $ref: '#/components/responses/unauthorized'
    get:
      x-eov-operation-id: getWorkflows
      x-eov-operation-handler: v1/handlers/workflows/workflows.handler
      tags:
        - Workflow
      summary: Retrieve all workflows
      description: Retrieve all workflows from your instance.
      parameters:
        - name: active
          in: query
          schema:
            type: boolean
            example: true
        - name: tags
          in: query
          required: false
          explode: false
          allowReserved: true
          schema:
            type: string
            example: test,production
        - name: name
          in: query
          required: false
          explode: false
          allowReserved: true
          schema:
            type: string
            example: My Workflow
        - name: projectId
          in: query
          required: false
          explode: false
          allowReserved: true
          schema:
            type: string
            example: VmwOO9HeTEj20kxM
        - name: excludePinnedData
          in: query
          required: false
          description: Set this to avoid retrieving pinned data
          schema:
            type: boolean
            example: true
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/cursor'
      responses:
        '200':
          description: Operation successful.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workflowList'
        '401':
          $ref: '#/components/responses/unauthorized'
  /workflows/{id}:
    get:
      x-eov-operation-id: getWorkflow
      x-eov-operation-handler: v1/handlers/workflows/workflows.handler
      tags:
        - Workflow
      summary: Retrieves a workflow
      description: Retrieves a workflow.
      parameters:
        - name: excludePinnedData
          in: query
          required: false
          description: Set this to avoid retrieving pinned data
          schema:
            type: boolean
            example: true
        - $ref: '#/components/parameters/workflowId'
      responses:
        '200':
          description: Operation successful.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workflow'
        '401':
          $ref: '#/components/responses/unauthorized'
        '404':
          $ref: '#/components/responses/notFound'
    delete:
      x-eov-operation-id: deleteWorkflow
      x-eov-operation-handler: v1/handlers/workflows/workflows.handler
      tags:
        - Workflow
      summary: Delete a workflow
      description: Deletes a workflow.
      parameters:
        - $ref: '#/components/parameters/workflowId'
      responses:
        '200':
          description: Operation successful.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workflow'
        '401':
          $ref: '#/components/responses/unauthorized'
        '404':
          $ref: '#/components/responses/notFound'
    put:
      x-eov-operation-id: updateWorkflow
      x-eov-operation-handler: v1/handlers/workflows/workflows.handler
      tags:
        - Workflow
      summary: Update a workflow
      description: Update a workflow.
      parameters:
        - $ref: '#/components/parameters/workflowId'
      requestBody:
        description: Updated workflow object.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workflow'
        required: true
      responses:
        '200':
          description: Workflow object
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workflow'
        '400':
          $ref: '#/components/responses/badRequest'
        '401':
          $ref: '#/components/responses/unauthorized'
        '404':
          $ref: '#/components/responses/notFound'
  /workflows/{id}/activate:
    post:
      x-eov-operation-id: activateWorkflow
      x-eov-operation-handler: v1/handlers/workflows/workflows.handler
      tags:
        - Workflow
      summary: Activate a workflow
      description: Active a workflow.
      parameters:
        - $ref: '#/components/parameters/workflowId'
      responses:
        '200':
          description: Workflow object
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workflow'
        '401':
          $ref: '#/components/responses/unauthorized'
        '404':
          $ref: '#/components/responses/notFound'
  /workflows/{id}/deactivate:
    post:
      x-eov-operation-id: deactivateWorkflow
      x-eov-operation-handler: v1/handlers/workflows/workflows.handler
      tags:
        - Workflow
      summary: Deactivate a workflow
      description: Deactivate a workflow.
      parameters:
        - $ref: '#/components/parameters/workflowId'
      responses:
        '200':
          description: Workflow object
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workflow'
        '401':
          $ref: '#/components/responses/unauthorized'
        '404':
          $ref: '#/components/responses/notFound'
  /workflows/{id}/transfer:
    put:
      x-eov-operation-id: transferWorkflow
      x-eov-operation-handler: v1/handlers/workflows/workflows.handler
      tags:
        - Workflow
      summary: Transfer a workflow to another project.
      description: Transfer a workflow to another project.
      parameters:
        - $ref: '#/components/parameters/workflowId'
      requestBody:
        description: Destination project information for the workflow transfer.
        content:
          application/json:
            schema:
              type: object
              properties:
                destinationProjectId:
                  type: string
                  description: The ID of the project to transfer the workflow to.
              required:
                - destinationProjectId
        required: true
      responses:
        '200':
          description: Operation successful.
        '400':
          $ref: '#/components/responses/badRequest'
        '401':
          $ref: '#/components/responses/unauthorized'
        '404':
          $ref: '#/components/responses/notFound'
  /credentials/{id}/transfer:
    put:
      x-eov-operation-id: transferCredential
      x-eov-operation-handler: v1/handlers/credentials/credentials.handler
      tags:
        - Workflow
      summary: Transfer a credential to another project.
      description: Transfer a credential to another project.
      parameters:
        - $ref: '#/components/parameters/credentialId'
      requestBody:
        description: Destination project for the credential transfer.
        content:
          application/json:
            schema:
              type: object
              properties:
                destinationProjectId:
                  type: string
                  description: The ID of the project to transfer the credential to.
              required:
                - destinationProjectId
        required: true
      responses:
        '200':
          description: Operation successful.
        '400':
          $ref: '#/components/responses/badRequest'
        '401':
          $ref: '#/components/responses/unauthorized'
        '404':
          $ref: '#/components/responses/notFound'
  /workflows/{id}/tags:
    get:
      x-eov-operation-id: getWorkflowTags
      x-eov-operation-handler: v1/handlers/workflows/workflows.handler
      tags:
        - Workflow
      summary: Get workflow tags
      description: Get workflow tags.
      parameters:
        - $ref: '#/components/parameters/workflowId'
      responses:
        '200':
          description: List of tags
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workflowTags'
        '400':
          $ref: '#/components/responses/badRequest'
        '401':
          $ref: '#/components/responses/unauthorized'
        '404':
          $ref: '#/components/responses/notFound'
    put:
      x-eov-operation-id: updateWorkflowTags
      x-eov-operation-handler: v1/handlers/workflows/workflows.handler
      tags:
        - Workflow
      summary: Update tags of a workflow
      description: Update tags of a workflow.
      parameters:
        - $ref: '#/components/parameters/workflowId'
      requestBody:
        description: List of tags
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/tagIds'
        required: true
      responses:
        '200':
          description: List of tags after add the tag
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/workflowTags'
        '400':
          $ref: '#/components/responses/badRequest'
        '401':
          $ref: '#/components/responses/unauthorized'
        '404':
          $ref: '#/components/responses/notFound'
  /users:
    get:
      x-eov-operation-id: getUsers
      x-eov-operation-handler: v1/handlers/users/users.handler.ee
      tags:
        - User
      summary: Retrieve all users
      description: Retrieve all users from your instance. Only available for the instance owner.
      parameters:
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/cursor'
        - $ref: '#/components/parameters/includeRole'
        - name: projectId
          in: query
          required: false
          explode: false
          allowReserved: true
          schema:
            type: string
            example: VmwOO9HeTEj20kxM
      responses:
        '200':
          description: Operation successful.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/userList'
        '401':
          $ref: '#/components/responses/unauthorized'
    post:
      x-eov-operation-id: createUser
      x-eov-operation-handler: v1/handlers/users/users.handler.ee
      tags:
        - User
      summary: Create multiple users
      description: Create one or more users.
      requestBody:
        description: Array of users to be created.
        required: true
        content:
          application/json:
            schema:
              type: array
              items:
                type: object
                properties:
                  email:
                    type: string
                    format: email
                  role:
                    type: string
                    enum:
                      - global:admin
                      - global:member
                required:
                  - email
      responses:
        '200':
          description: Operation successful.
          content:
            application/json:
              schema:
                type: object
                properties:
                  user:
                    type: object
                    properties:
                      id:
                        type: string
                      email:
                        type: string
                      inviteAcceptUrl:
                        type: string
                      emailSent:
                        type: boolean
                  error:
                    type: string
        '401':
          $ref: '#/components/responses/unauthorized'
        '403':
          $ref: '#/components/responses/forbidden'
  /users/{id}:
    get:
      x-eov-operation-id: getUser
      x-eov-operation-handler: v1/handlers/users/users.handler.ee
      tags:
        - User
      summary: Get user by ID/Email
      description: Retrieve a user from your instance. Only available for the instance owner.
      parameters:
        - $ref: '#/components/parameters/userIdentifier'
        - $ref: '#/components/parameters/includeRole'
      responses:
        '200':
          description: Operation successful.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/user'
        '401':
          $ref: '#/components/responses/unauthorized'
    delete:
      x-eov-operation-id: deleteUser
      x-eov-operation-handler: v1/handlers/users/users.handler.ee
      tags:
        - User
      summary: Delete a user
      description: Delete a user from your instance.
      parameters:
        - $ref: '#/components/parameters/userIdentifier'
      responses:
        '204':
          description: Operation successful.
        '401':
          $ref: '#/components/responses/unauthorized'
        '403':
          $ref: '#/components/responses/forbidden'
        '404':
          $ref: '#/components/responses/notFound'
  /users/{id}/role:
    patch:
      x-eov-operation-id: changeRole
      x-eov-operation-handler: v1/handlers/users/users.handler.ee
      tags:
        - User
      summary: Change a user's global role
      description: Change a user's global role
      parameters:
        - $ref: '#/components/parameters/userIdentifier'
      requestBody:
        description: New role for the user
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                newRoleName:
                  type: string
                  enum:
                    - global:admin
                    - global:member
              required:
                - newRoleName
      responses:
        '200':
          description: Operation successful.
        '401':
          $ref: '#/components/responses/unauthorized'
        '403':
          $ref: '#/components/responses/forbidden'
        '404':
          $ref: '#/components/responses/notFound'
  /source-control/pull:
    post:
      x-eov-operation-id: pull
      x-eov-operation-handler: v1/handlers/source-control/source-control.handler
      tags:
        - SourceControl
      summary: Pull changes from the remote repository
      description: Requires the Source Control feature to be licensed and connected to a repository.
      requestBody:
        description: Pull options
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/pull'
      responses:
        '200':
          description: Import result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/importResult'
        '400':
          $ref: '#/components/responses/badRequest'
        '409':
          $ref: '#/components/responses/conflict'
  /variables:
    post:
      x-eov-operation-id: createVariable
      x-eov-operation-handler: v1/handlers/variables/variables.handler
      tags:
        - Variables
      summary: Create a variable
      description: Create a variable in your instance.
      requestBody:
        description: Payload for variable to create.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/variable'
        required: true
      responses:
        '201':
          description: Operation successful.
        '400':
          $ref: '#/components/responses/badRequest'
        '401':
          $ref: '#/components/responses/unauthorized'
    get:
      x-eov-operation-id: getVariables
      x-eov-operation-handler: v1/handlers/variables/variables.handler
      tags:
        - Variables
      summary: Retrieve variables
      description: Retrieve variables from your instance.
      parameters:
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/cursor'
      responses:
        '200':
          description: Operation successful.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/variableList'
        '401':
          $ref: '#/components/responses/unauthorized'
  /variables/{id}:
    delete:
      x-eov-operation-id: deleteVariable
      x-eov-operation-handler: v1/handlers/variables/variables.handler
      tags:
        - Variables
      summary: Delete a variable
      description: Delete a variable from your instance.
      parameters:
        - $ref: '#/components/parameters/variableId'
      responses:
        '204':
          description: Operation successful.
        '401':
          $ref: '#/components/responses/unauthorized'
        '404':
          $ref: '#/components/responses/notFound'
  /projects:
    post:
      x-eov-operation-id: createProject
      x-eov-operation-handler: v1/handlers/projects/projects.handler
      tags:
        - Projects
      summary: Create a project
      description: Create a project in your instance.
      requestBody:
        description: Payload for project to create.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/project'
        required: true
      responses:
        '201':
          description: Operation successful.
        '400':
          $ref: '#/components/responses/badRequest'
        '401':
          $ref: '#/components/responses/unauthorized'
    get:
      x-eov-operation-id: getProjects
      x-eov-operation-handler: v1/handlers/projects/projects.handler
      tags:
        - Projects
      summary: Retrieve projects
      description: Retrieve projects from your instance.
      parameters:
        - $ref: '#/components/parameters/limit'
        - $ref: '#/components/parameters/cursor'
      responses:
        '200':
          description: Operation successful.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/projectList'
        '401':
          $ref: '#/components/responses/unauthorized'
  /projects/{projectId}:
    delete:
      x-eov-operation-id: deleteProject
      x-eov-operation-handler: v1/handlers/projects/projects.handler
      tags:
        - Projects
      summary: Delete a project
      description: Delete a project from your instance.
      parameters:
        - $ref: '#/components/parameters/projectId'
      responses:
        '204':
          description: Operation successful.
        '401':
          $ref: '#/components/responses/unauthorized'
        '403':
          $ref: '#/components/responses/forbidden'
        '404':
          $ref: '#/components/responses/notFound'
    put:
      x-eov-operation-id: updateProject
      x-eov-operation-handler: v1/handlers/projects/projects.handler
      tags:
        - Project
      summary: Update a project
      description: Update a project.
      requestBody:
        description: Updated project object.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/project'
        required: true
      responses:
        '204':
          description: Operation successful.
        '400':
          $ref: '#/components/responses/badRequest'
        '401':
          $ref: '#/components/responses/unauthorized'
        '403':
          $ref: '#/components/responses/forbidden'
        '404':
          $ref: '#/components/responses/notFound'
components:
  schemas:
    audit:
      type: object
      properties:
        Credentials Risk Report:
          type: object
          example:
            risk: credentials
            sections:
              - title: Credentials not used in any workflow
                description: These credentials are not used in any workflow. Keeping unused credentials in your instance is an unneeded security risk.
                recommendation: Consider deleting these credentials if you no longer need them.
                location:
                  - kind: credential
                    id: '1'
                    name: My Test Account
        Database Risk Report:
          type: object
          example:
            risk: database
            sections:
              - title: Expressions in "Execute Query" fields in SQL nodes
                description: This SQL node has an expression in the "Query" field of an "Execute Query" operation. Building a SQL query with an expression may lead to a SQL injection attack.
                recommendation: Consider using the "Query Parameters" field to pass parameters to the query
                or validating the input of the expression in the "Query" field.: null
                location:
                  - kind: node
                    workflowId: '1'
                    workflowName: My Workflow
                    nodeId: 51eb5852-ce0b-4806-b4ff-e41322a4041a
                    nodeName: MySQL
                    nodeType: n8n-nodes-base.mySql
        Filesystem Risk Report:
          type: object
          example:
            risk: filesystem
            sections:
              - title: Nodes that interact with the filesystem
                description: This node reads from and writes to any accessible file in the host filesystem. Sensitive file content may be manipulated through a node operation.
                recommendation: Consider protecting any sensitive files in the host filesystem
                or refactoring the workflow so that it does not require host filesystem interaction.: null
                location:
                  - kind: node
                    workflowId: '1'
                    workflowName: My Workflow
                    nodeId: 51eb5852-ce0b-4806-b4ff-e41322a4041a
                    nodeName: Ready Binary file
                    nodeType: n8n-nodes-base.readBinaryFile
        Nodes Risk Report:
          type: object
          example:
            risk: nodes
            sections:
              - title: Community nodes
                description: This node is sourced from the community. Community nodes are not vetted by the n8n team and have full access to the host system.
                recommendation: Consider reviewing the source code in any community nodes installed in this n8n instance
                and uninstalling any community nodes no longer used.: null
                location:
                  - kind: community
                    nodeType: n8n-nodes-test.test
                    packageUrl: https://www.npmjs.com/package/n8n-nodes-test
        Instance Risk Report:
          type: object
          example:
            risk: execution
            sections:
              - title: Unprotected webhooks in instance
                description: These webhook nodes have the "Authentication" field set to "None" and are not directly connected to a node to validate the payload. Every unprotected webhook allows your workflow to be called by any third party who knows the webhook URL.
                recommendation: Consider setting the "Authentication" field to an option other than "None"
                or validating the payload with one of the following nodes.: null
                location:
                  - kind: community
                    nodeType: n8n-nodes-test.test
                    packageUrl: https://www.npmjs.com/package/n8n-nodes-test
    credential:
      required:
        - name
        - type
        - data
      type: object
      properties:
        id:
          type: string
          readOnly: true
          example: R2DjclaysHbqn778
        name:
          type: string
          example: Joe's Github Credentials
        type:
          type: string
          example: github
        data:
          type: object
          writeOnly: true
          example:
            token: ada612vad6fa5df4adf5a5dsf4389adsf76da7s
        createdAt:
          type: string
          format: date-time
          readOnly: true
          example: '2022-04-29T11:02:29.842Z'
        updatedAt:
          type: string
          format: date-time
          readOnly: true
          example: '2022-04-29T11:02:29.842Z'
    create-credential-response:
      required:
        - id
        - name
        - type
        - createdAt
        - updatedAt
      type: object
      properties:
        id:
          type: string
          readOnly: true
          example: vHxaz5UaCghVYl9C
        name:
          type: string
          example: John's Github account
        type:
          type: string
          example: github
        createdAt:
          type: string
          format: date-time
          readOnly: true
          example: '2022-04-29T11:02:29.842Z'
        updatedAt:
          type: string
          format: date-time
          readOnly: true
          example: '2022-04-29T11:02:29.842Z'
    execution:
      type: object
      properties:
        id:
          type: number
          example: 1000
        data:
          type: object
        finished:
          type: boolean
          example: true
        mode:
          type: string
          enum:
            - cli
            - error
            - integrated
            - internal
            - manual
            - retry
            - trigger
            - webhook
        retryOf:
          type: number
          nullable: true
        retrySuccessId:
          type: number
          nullable: true
          example: '2'
        startedAt:
          type: string
          format: date-time
        stoppedAt:
          type: string
          format: date-time
        workflowId:
          type: number
          example: '1000'
        waitTill:
          type: string
          nullable: true
          format: date-time
        customData:
          type: object
    executionList:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/execution'
        nextCursor:
          type: string
          description: Paginate through executions by setting the cursor parameter to a nextCursor attribute returned by a previous request. Default value fetches the first "page" of the collection.
          nullable: true
          example: MTIzZTQ1NjctZTg5Yi0xMmQzLWE0NTYtNDI2NjE0MTc0MDA
    tag:
      type: object
      additionalProperties: false
      required:
        - name
      properties:
        id:
          type: string
          readOnly: true
          example: 2tUt1wbLX592XDdX
        name:
          type: string
          example: Production
        createdAt:
          type: string
          format: date-time
          readOnly: true
        updatedAt:
          type: string
          format: date-time
          readOnly: true
    tagList:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/tag'
        nextCursor:
          type: string
          description: Paginate through tags by setting the cursor parameter to a nextCursor attribute returned by a previous request. Default value fetches the first "page" of the collection.
          nullable: true
          example: MTIzZTQ1NjctZTg5Yi0xMmQzLWE0NTYtNDI2NjE0MTc0MDA
    node:
      type: object
      additionalProperties: false
      properties:
        id:
          type: string
          example: 0f5532f9-36ba-4bef-86c7-30d607400b15
        name:
          type: string
          example: Jira
        webhookId:
          type: string
        disabled:
          type: boolean
        notesInFlow:
          type: boolean
        notes:
          type: string
        type:
          type: string
          example: n8n-nodes-base.Jira
        typeVersion:
          type: number
          example: 1
        executeOnce:
          type: boolean
          example: false
        alwaysOutputData:
          type: boolean
          example: false
        retryOnFail:
          type: boolean
          example: false
        maxTries:
          type: number
        waitBetweenTries:
          type: number
        continueOnFail:
          type: boolean
          example: false
          description: use onError instead
          deprecated: true
        onError:
          type: string
          example: stopWorkflow
        position:
          type: array
          items:
            type: number
          example:
            - -100
            - 80
        parameters:
          type: object
          example:
            additionalProperties: {}
        credentials:
          type: object
          example:
            jiraSoftwareCloudApi:
              id: '35'
              name: jiraApi
        createdAt:
          type: string
          format: date-time
          readOnly: true
        updatedAt:
          type: string
          format: date-time
          readOnly: true
    workflowSettings:
      type: object
      additionalProperties: false
      properties:
        saveExecutionProgress:
          type: boolean
        saveManualExecutions:
          type: boolean
        saveDataErrorExecution:
          type: string
          enum:
            - all
            - none
        saveDataSuccessExecution:
          type: string
          enum:
            - all
            - none
        executionTimeout:
          type: number
          example: 3600
          maxLength: 3600
        errorWorkflow:
          type: string
          example: VzqKEW0ShTXA5vPj
          description: The ID of the workflow that contains the error trigger node.
        timezone:
          type: string
          example: America/New_York
        executionOrder:
          type: string
          example: v1
    workflow:
      type: object
      additionalProperties: false
      required:
        - name
        - nodes
        - connections
        - settings
      properties:
        id:
          type: string
          readOnly: true
          example: 2tUt1wbLX592XDdX
        name:
          type: string
          example: Workflow 1
        active:
          type: boolean
          readOnly: true
        createdAt:
          type: string
          format: date-time
          readOnly: true
        updatedAt:
          type: string
          format: date-time
          readOnly: true
        nodes:
          type: array
          items:
            $ref: '#/components/schemas/node'
        connections:
          type: object
          example:
            main:
              - node: Jira
                type: main
                index: 0
        settings:
          $ref: '#/components/schemas/workflowSettings'
        staticData:
          example:
            lastId: 1
          anyOf:
            - type: string
              format: jsonString
              nullable: true
            - type: object
              nullable: true
        tags:
          type: array
          items:
            $ref: '#/components/schemas/tag'
          readOnly: true
    workflowList:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/workflow'
        nextCursor:
          type: string
          description: Paginate through workflows by setting the cursor parameter to a nextCursor attribute returned by a previous request. Default value fetches the first "page" of the collection.
          nullable: true
          example: MTIzZTQ1NjctZTg5Yi0xMmQzLWE0NTYtNDI2NjE0MTc0MDA
    workflowTags:
      type: array
      items:
        $ref: '#/components/schemas/tag'
    tagIds:
      type: array
      items:
        type: object
        additionalProperties: false
        required:
          - id
        properties:
          id:
            type: string
            example: 2tUt1wbLX592XDdX
    user:
      required:
        - email
      type: object
      properties:
        id:
          type: string
          readOnly: true
          example: 123e4567-e89b-12d3-a456-426614174000
        email:
          type: string
          format: email
          example: [email protected]
        firstName:
          maxLength: 32
          type: string
          description: User's first name
          readOnly: true
          example: john
        lastName:
          maxLength: 32
          type: string
          description: User's last name
          readOnly: true
          example: Doe
        isPending:
          type: boolean
          description: Whether the user finished setting up their account in response to the invitation (true) or not (false).
          readOnly: true
        createdAt:
          type: string
          description: Time the user was created.
          format: date-time
          readOnly: true
        updatedAt:
          type: string
          description: Last time the user was updated.
          format: date-time
          readOnly: true
        role:
          type: string
          example: owner
          readOnly: true
    userList:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/user'
        nextCursor:
          type: string
          description: Paginate through users by setting the cursor parameter to a nextCursor attribute returned by a previous request. Default value fetches the first "page" of the collection.
          nullable: true
          example: MTIzZTQ1NjctZTg5Yi0xMmQzLWE0NTYtNDI2NjE0MTc0MDA
    pull:
      type: object
      properties:
        force:
          type: boolean
          example: true
        variables:
          type: object
          example:
            foo: bar
    importResult:
      type: object
      additionalProperties: true
      properties:
        variables:
          type: object
          properties:
            added:
              type: array
              items:
                type: string
            changed:
              type: array
              items:
                type: string
        credentials:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
              name:
                type: string
              type:
                type: string
        workflows:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
              name:
                type: string
        tags:
          type: object
          properties:
            tags:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: string
                  name:
                    type: string
            mappings:
              type: array
              items:
                type: object
                properties:
                  workflowId:
                    type: string
                  tagId:
                    type: string
    variable:
      type: object
      additionalProperties: false
      required:
        - key
        - value
      properties:
        id:
          type: string
          readOnly: true
        key:
          type: string
        value:
          type: string
          example: test
        type:
          type: string
          readOnly: true
    variableList:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/variable'
        nextCursor:
          type: string
          description: Paginate through variables by setting the cursor parameter to a nextCursor attribute returned by a previous request. Default value fetches the first "page" of the collection.
          nullable: true
          example: MTIzZTQ1NjctZTg5Yi0xMmQzLWE0NTYtNDI2NjE0MTc0MDA
    project:
      type: object
      additionalProperties: false
      required:
        - name
      properties:
        id:
          type: string
          readOnly: true
        name:
          type: string
        type:
          type: string
          readOnly: true
    projectList:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/project'
        nextCursor:
          type: string
          description: Paginate through projects by setting the cursor parameter to a nextCursor attribute returned by a previous request. Default value fetches the first "page" of the collection.
          nullable: true
          example: MTIzZTQ1NjctZTg5Yi0xMmQzLWE0NTYtNDI2NjE0MTc0MDA
    error:
      required:
        - message
      type: object
      properties:
        code:
          type: string
        message:
          type: string
        description:
          type: string
    role:
      readOnly: true
      type: object
      properties:
        id:
          type: number
          readOnly: true
          example: 1
        name:
          type: string
          example: owner
          readOnly: true
        scope:
          type: string
          readOnly: true
          example: global
        createdAt:
          type: string
          description: Time the role was created.
          format: date-time
          readOnly: true
        updatedAt:
          type: string
          description: Last time the role was updated.
          format: date-time
          readOnly: true
    credentialType:
      type: object
      properties:
        displayName:
          type: string
          readOnly: true
          example: Email
        name:
          type: string
          readOnly: true
          example: email
        type:
          type: string
          readOnly: true
          example: string
        default:
          type: string
          readOnly: true
          example: string
    Error:
      $ref: '#/components/schemas/error'
    Role:
      $ref: '#/components/schemas/role'
    Execution:
      $ref: '#/components/schemas/execution'
    Node:
      $ref: '#/components/schemas/node'
    Tag:
      $ref: '#/components/schemas/tag'
    Workflow:
      $ref: '#/components/schemas/workflow'
    WorkflowSettings:
      $ref: '#/components/schemas/workflowSettings'
    ExecutionList:
      $ref: '#/components/schemas/executionList'
    WorkflowList:
      $ref: '#/components/schemas/workflowList'
    Credential:
      $ref: '#/components/schemas/credential'
    CredentialType:
      $ref: '#/components/schemas/credentialType'
    Audit:
      $ref: '#/components/schemas/audit'
    Pull:
      $ref: '#/components/schemas/pull'
    ImportResult:
      $ref: '#/components/schemas/importResult'
    UserList:
      $ref: '#/components/schemas/userList'
    User:
      $ref: '#/components/schemas/user'
  responses:
    unauthorized:
      description: Unauthorized
    notFound:
      description: The specified resource was not found.
    badRequest:
      description: The request is invalid or provides malformed data.
    conflict:
      description: Conflict
    forbidden:
      description: Forbidden
    NotFound:
      $ref: '#/components/responses/notFound'
    Unauthorized:
      $ref: '#/components/responses/unauthorized'
    BadRequest:
      $ref: '#/components/responses/badRequest'
    Conflict:
      $ref: '#/components/responses/conflict'
    Forbidden:
      $ref: '#/components/responses/forbidden'
  parameters:
    includeData:
      name: includeData
      in: query
      description: Whether or not to include the execution's detailed data.
      required: false
      schema:
        type: boolean
    limit:
      name: limit
      in: query
      description: The maximum number of items to return.
      required: false
      schema:
        type: number
        example: 100
        default: 100
        maximum: 250
    cursor:
      name: cursor
      in: query
      description: Paginate by setting the cursor parameter to the nextCursor attribute returned by the previous request's response. Default value fetches the first "page" of the collection. See pagination for more detail.
      required: false
      style: form
      schema:
        type: string
    executionId:
      name: id
      in: path
      description: The ID of the execution.
      required: true
      schema:
        type: number
    tagId:
      name: id
      in: path
      description: The ID of the tag.
      required: true
      schema:
        type: string
    workflowId:
      name: id
      in: path
      description: The ID of the workflow.
      required: true
      schema:
        type: string
    credentialId:
      name: id
      in: path
      description: The ID of the credential.
      required: true
      schema:
        type: string
    includeRole:
      name: includeRole
      in: query
      description: Whether to include the user's role or not.
      required: false
      schema:
        type: boolean
        example: true
        default: false
    userIdentifier:
      name: id
      in: path
      description: The ID or email of the user.
      required: true
      schema:
        type: string
        format: identifier
    variableId:
      name: id
      in: path
      description: The ID of the variable.
      required: true
      schema:
        type: string
    projectId:
      name: projectId
      in: path
      description: The ID of the project.
      required: true
      schema:
        type: string
    Cursor:
      $ref: '#/components/parameters/cursor'
    Limit:
      $ref: '#/components/parameters/limit'
    ExecutionId:
      $ref: '#/components/parameters/executionId'
    WorkflowId:
      $ref: '#/components/parameters/workflowId'
    TagId:
      $ref: '#/components/parameters/tagId'
    IncludeData:
      $ref: '#/components/parameters/includeData'
    UserIdentifier:
      $ref: '#/components/parameters/userIdentifier'
    IncludeRole:
      $ref: '#/components/parameters/includeRole'
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-N8N-API-KEY

```
Page 2/2FirstPrevNextLast