├── .editorconfig
├── .eslintrc.js
├── .eslintrc.prepublish.js
├── .github
├── dependabot.yml
├── pull_request_template.md
└── workflows
│ ├── ci-cd.yml
│ └── pr-validation.yml
├── .gitignore
├── .npmignore
├── .nvmrc
├── .prettierrc.js
├── .vscode
└── extensions.json
├── CODE_OF_CONDUCT.md
├── LICENSE.md
├── README.md
├── __tests__
└── McpClient.test.ts
├── assets
├── brave-search-example.png
├── credentials-envs.png
├── credentials.png
├── execute-tool-result.png
├── execute-tool.png
├── list-tools.png
├── mcp-n8n.png
├── multi-server-example.png
├── operations.png
├── sse-credentials.png
└── sse-example.png
├── credentials
├── McpClientApi.credentials.ts
├── McpClientHttpApi.credentials.ts
├── McpClientSseApi.credentials.ts
└── mcpClient.svg
├── gulpfile.js
├── index.js
├── jest.config.js
├── nodes
└── McpClient
│ ├── McpClient.node.test.ts
│ ├── McpClient.node.ts
│ └── mcpClient.svg
├── package-lock.json
├── package.json
├── pnpm-lock.yaml
└── tsconfig.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = tab
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [package.json]
12 | indent_style = space
13 | indent_size = 2
14 |
15 | [*.md]
16 | trim_trailing_whitespace = false
17 |
18 | [*.yml]
19 | indent_style = space
20 | indent_size = 2
21 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @type {import('@types/eslint').ESLint.ConfigData}
3 | */
4 | module.exports = {
5 | root: true,
6 |
7 | env: {
8 | browser: true,
9 | es6: true,
10 | node: true,
11 | },
12 |
13 | parser: '@typescript-eslint/parser',
14 |
15 | parserOptions: {
16 | project: ['./tsconfig.json'],
17 | sourceType: 'module',
18 | extraFileExtensions: ['.json'],
19 | },
20 |
21 | ignorePatterns: ['.eslintrc.js', '**/*.js', '**/node_modules/**', '**/dist/**'],
22 |
23 | overrides: [
24 | {
25 | files: ['package.json'],
26 | plugins: ['eslint-plugin-n8n-nodes-base'],
27 | extends: ['plugin:n8n-nodes-base/community'],
28 | rules: {
29 | 'n8n-nodes-base/community-package-json-name-still-default': 'off',
30 | },
31 | },
32 | {
33 | files: ['./credentials/**/*.ts'],
34 | plugins: ['eslint-plugin-n8n-nodes-base'],
35 | extends: ['plugin:n8n-nodes-base/credentials'],
36 | rules: {
37 | 'n8n-nodes-base/cred-class-field-documentation-url-missing': 'off',
38 | 'n8n-nodes-base/cred-class-field-documentation-url-miscased': 'off',
39 | },
40 | },
41 | {
42 | files: ['./nodes/**/*.ts'],
43 | plugins: ['eslint-plugin-n8n-nodes-base'],
44 | extends: ['plugin:n8n-nodes-base/nodes'],
45 | rules: {
46 | 'n8n-nodes-base/node-execute-block-missing-continue-on-fail': 'off',
47 | 'n8n-nodes-base/node-resource-description-filename-against-convention': 'off',
48 | 'n8n-nodes-base/node-param-fixed-collection-type-unsorted-items': 'off',
49 | 'n8n-nodes-base/node-class-description-inputs-wrong-regular-node': 'off',
50 | 'n8n-nodes-base/node-class-description-outputs-wrong': 'off',
51 | },
52 | },
53 | ],
54 | };
55 |
--------------------------------------------------------------------------------
/.eslintrc.prepublish.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @type {import('@types/eslint').ESLint.ConfigData}
3 | */
4 | module.exports = {
5 | extends: "./.eslintrc.js",
6 |
7 | overrides: [
8 | {
9 | files: ['package.json'],
10 | plugins: ['eslint-plugin-n8n-nodes-base'],
11 | rules: {
12 | 'n8n-nodes-base/community-package-json-name-still-default': 'error',
13 | },
14 | },
15 | ],
16 | };
17 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "npm"
4 | directory: "/"
5 | schedule:
6 | interval: "weekly"
7 | open-pull-requests-limit: 10
8 | versioning-strategy: increase
9 |
10 | - package-ecosystem: "github-actions"
11 | directory: "/"
12 | schedule:
13 | interval: "weekly"
14 | open-pull-requests-limit: 5
15 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## Description
2 |
3 |
4 | ## Related Issue
5 |
6 | Closes #
7 |
8 | ## Type of Change
9 |
10 | - [ ] Bug fix (non-breaking change that fixes an issue)
11 | - [ ] New feature (non-breaking change that adds functionality)
12 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
13 | - [ ] Documentation update
14 | - [ ] Other (please describe):
15 |
16 | ## How Has This Been Tested?
17 |
18 |
19 | ## Checklist
20 | - [ ] My code follows the code style of this project
21 | - [ ] I have updated the documentation accordingly
22 | - [ ] I have added tests to cover my changes
23 | - [ ] All new and existing tests passed
24 |
25 | ## Release Notes
26 |
27 |
28 | ## Screenshots (if applicable)
29 |
30 |
--------------------------------------------------------------------------------
/.github/workflows/ci-cd.yml:
--------------------------------------------------------------------------------
1 | name: CI/CD Pipeline
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: [main]
8 | workflow_dispatch:
9 | inputs:
10 | release_type:
11 | description: 'Release type (patch, minor, major)'
12 | required: true
13 | default: 'patch'
14 | type: choice
15 | options:
16 | - patch
17 | - minor
18 | - major
19 |
20 | jobs:
21 | build-and-test:
22 | runs-on: ubuntu-latest
23 | steps:
24 | - uses: actions/checkout@v4
25 |
26 | - name: Setup Node.js
27 | uses: actions/setup-node@v4
28 | with:
29 | node-version: '18'
30 | cache: 'npm'
31 |
32 | - name: Install dependencies
33 | run: npm ci
34 |
35 | - name: Lint
36 | run: npm run lint --if-present || echo "Linting failed but continuing"
37 |
38 | - name: Build
39 | run: npm run build --if-present || echo "Build script not found or build failed but continuing"
40 |
41 | - name: Test
42 | run: npm run test --if-present || echo "No tests found or tests failed but continuing"
43 |
44 | - name: Upload build artifacts
45 | uses: actions/upload-artifact@v4
46 | with:
47 | name: dist
48 | path: dist/
49 |
50 | release:
51 | needs: build-and-test
52 | if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/main')
53 | runs-on: ubuntu-latest
54 | permissions:
55 | contents: write
56 | packages: write
57 | steps:
58 | - uses: actions/checkout@v4
59 | with:
60 | fetch-depth: 0
61 | token: ${{ secrets.CICD_TOKEN }}
62 |
63 | - name: Setup Node.js
64 | uses: actions/setup-node@v4
65 | with:
66 | node-version: '18'
67 | registry-url: 'https://registry.npmjs.org'
68 |
69 | - name: Install dependencies
70 | run: npm ci
71 |
72 | - name: Download build artifacts
73 | uses: actions/download-artifact@v4
74 | with:
75 | name: dist
76 | path: dist/
77 |
78 | - name: Configure Git
79 | run: |
80 | git config user.name "GitHub Actions"
81 | git config user.email "actions@github.com"
82 |
83 | - name: Bump version
84 | id: bump_version
85 | if: github.event_name == 'workflow_dispatch'
86 | run: |
87 | RELEASE_TYPE="${{ github.event.inputs.release_type }}"
88 | npm version $RELEASE_TYPE -m "Bump version to %s [skip ci]"
89 | echo "new_version=$(npm pkg get version | tr -d '"')" >> $GITHUB_OUTPUT
90 |
91 | - name: Auto bump patch version
92 | id: auto_bump
93 | if: github.event_name == 'push' && github.ref == 'refs/heads/main'
94 | run: |
95 | npm version patch -m "Bump version to %s [skip ci]"
96 | echo "new_version=$(npm pkg get version | tr -d '"')" >> $GITHUB_OUTPUT
97 |
98 | - name: Push changes
99 | run: |
100 | VERSION=$(npm pkg get version | tr -d '"')
101 | git push
102 | git push origin v$VERSION
103 |
104 | - name: Create GitHub Release
105 | uses: softprops/action-gh-release@v1
106 | with:
107 | tag_name: v${{ steps.bump_version.outputs.new_version || steps.auto_bump.outputs.new_version }}
108 | name: Release v${{ steps.bump_version.outputs.new_version || steps.auto_bump.outputs.new_version }}
109 | draft: false
110 | generate_release_notes: true
111 |
112 | - name: Publish to npm
113 | run: npm publish --access public
114 | env:
115 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
116 |
--------------------------------------------------------------------------------
/.github/workflows/pr-validation.yml:
--------------------------------------------------------------------------------
1 | name: PR Validation
2 |
3 | on:
4 | pull_request:
5 | branches: [main]
6 | types: [opened, synchronize, reopened]
7 |
8 | jobs:
9 | validate:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v3
13 |
14 | - uses: actions/setup-node@v3
15 | with:
16 | node-version: '18'
17 | cache: 'npm'
18 |
19 | - name: Install dependencies
20 | run: npm ci
21 |
22 | - name: Lint
23 | run: npm run lint
24 |
25 | - name: Build
26 | run: npm run build
27 |
28 | - name: Test
29 | run: npm test
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | .tmp
4 | tmp
5 | dist
6 | npm-debug.log*
7 | yarn.lock
8 | .vscode/launch.json
9 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Git, GitHub and CI/CD
2 | .git
3 | .github
4 | .gitignore
5 |
6 | # Source and dev files
7 | src
8 | tests
9 | .eslintrc
10 | .prettierrc
11 | tsconfig.json
12 | jest.config.js
13 |
14 | # Documentation
15 | docs
16 | *.md
17 | !README.md
18 | !LICENSE.md
19 |
20 | # Misc
21 | .DS_Store
22 | .vscode
23 | coverage
24 | node_modules
25 |
26 | # Assets not needed in package
27 | assets
28 | screenshots
29 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v20.11.0
2 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | /**
3 | * https://prettier.io/docs/en/options.html#semicolons
4 | */
5 | semi: true,
6 |
7 | /**
8 | * https://prettier.io/docs/en/options.html#trailing-commas
9 | */
10 | trailingComma: 'all',
11 |
12 | /**
13 | * https://prettier.io/docs/en/options.html#bracket-spacing
14 | */
15 | bracketSpacing: true,
16 |
17 | /**
18 | * https://prettier.io/docs/en/options.html#tabs
19 | */
20 | useTabs: true,
21 |
22 | /**
23 | * https://prettier.io/docs/en/options.html#tab-width
24 | */
25 | tabWidth: 2,
26 |
27 | /**
28 | * https://prettier.io/docs/en/options.html#arrow-function-parentheses
29 | */
30 | arrowParens: 'always',
31 |
32 | /**
33 | * https://prettier.io/docs/en/options.html#quotes
34 | */
35 | singleQuote: true,
36 |
37 | /**
38 | * https://prettier.io/docs/en/options.html#quote-props
39 | */
40 | quoteProps: 'as-needed',
41 |
42 | /**
43 | * https://prettier.io/docs/en/options.html#end-of-line
44 | */
45 | endOfLine: 'lf',
46 |
47 | /**
48 | * https://prettier.io/docs/en/options.html#print-width
49 | */
50 | printWidth: 100,
51 | };
52 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "dbaeumer.vscode-eslint",
4 | "EditorConfig.EditorConfig",
5 | "esbenp.prettier-vscode",
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at jan@n8n.io. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright 2022 n8n
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7 | of the Software, and to permit persons to whom the Software is furnished to do
8 | so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://mseep.ai/app/bd76f121-1c8f-4f5d-9c65-1eac5d81b6af)
2 |
3 | # n8n-nodes-mcp-client
4 |
5 | > **Important Note:**
6 | > The Server-Sent Events (SSE) transport is deprecated and replaced by the new HTTP Streamable transport. SSE remains available for legacy compatibility, but HTTP Streamable is now the recommended method for all new implementations.
7 |
8 | This is an n8n community node that lets you interact with Model Context Protocol (MCP) servers in your n8n workflows.
9 |
10 | MCP is a protocol that enables AI models to interact with external tools and data sources in a standardized way. This node allows you to connect to MCP servers, access resources, execute tools, and use prompts.
11 |
12 | [n8n](https://n8n.io/) is a [fair-code licensed](https://docs.n8n.io/reference/license/) workflow automation platform.
13 |
14 | [Installation](#installation)
15 | [Credentials](#credentials)
16 | [Environment Variables](#environment-variables)
17 | [Operations](#operations)
18 | [Using as a Tool](#using-as-a-tool)
19 | [Compatibility](#compatibility)
20 | [Resources](#resources)
21 |
22 | ## Getting Started
23 |
24 | Official Quickstart Video:
25 |
26 | [](https://youtu.be/1t8DQL-jUJk)
27 |
28 |
29 |
30 | ### Community Videos
31 |
32 | Shoutout to all the creators of the following n8n community videos that are great resources for learning how to use this node:
33 |
34 | - [Is MCP the Future of N8N AI Agents? (Fully Tested!)](https://youtu.be/sb5hCcFYPIE)
35 | - [Connect N8N AI Agents to EVERYTHING using MCP?](https://youtu.be/tTDRgkD-120)
36 | - [Build an AI Agent That Can Use Any Tool (MCP in n8n Tutorial)](https://www.youtube.com/watch?v=SVZe2rdhYmA)
37 | - [The NEW N8N MCP is an Absolute Game-Changer (Brave Search MCP)](https://youtu.be/RxXS_FpJyGM)
38 | - [MCP & n8n Automation: The Ultimate Guide for MCP AI Agents (2025)](https://www.youtube.com/watch?v=mbQsnrxHPwE)
39 | - [REVOLUÇÃO na criação de AGENTES no N8N com o MCP Server!!!](https://www.youtube.com/watch?v=zgH85dJcs5c) (Portuguese)
40 |
41 | If you have a great video that you'd like to share, please let me know and I'll add it to the list!
42 |
43 | #### Interested a deeper dive into MCP?
44 |
45 | Check out my YouTube Series [MCP Explained](https://www.youtube.com/playlist?list=PLjOCx_PNfJ4S_oOSqrMi6t9_x1GllvQZO) for more information about the Model Context Protocol.
46 |
47 | ### Security Assessment
48 | [](https://mseep.ai/app/nerding-io-n8n-nodes-mcp)
49 |
50 |
51 | ## Installation
52 |
53 | Follow the [installation guide](https://docs.n8n.io/integrations/community-nodes/installation/) in the n8n community nodes documentation.
54 |
55 | Also pay attention to Environment Variables for [using tools in AI Agents](#using-as-a-tool). It's mandatory to set the `N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE` environment variable to `true` if you want to use the MCP Client node as a tool in AI Agents.
56 |
57 | ## Credentials
58 |
59 | The MCP Client node supports three types of credentials to connect to an MCP server:
60 |
61 | ### Command-line Based Transport (STDIO)
62 |
63 | 
64 |
65 | - **Command**: The command to start the MCP server
66 | - **Arguments**: Optional arguments to pass to the server command
67 | - **Environment Variables**: Variables to pass to the server in NAME=VALUE format
68 |
69 | ### HTTP Streamable Transport (Recommended)
70 |
71 | - **HTTP Streamable URL**: The HTTP endpoint that supports streaming responses (e.g., http://localhost:3001/stream)
72 | - **Additional Headers**: Optional headers to send with requests (format: name:value, one per line)
73 |
74 | HTTP Streamable is the recommended and modern method for all new integrations, providing better efficiency and flexibility compared to SSE.
75 |
76 | #### Example: Using a Local MCP Server with HTTP Streamable
77 |
78 | This example shows how to connect to a locally running MCP server using HTTP Streamable:
79 |
80 | 1. Start a local MCP server that supports HTTP Streamable:
81 | ```bash
82 | npx @modelcontextprotocol/server-example-streamable
83 | ```
84 |
85 | 2. Configure MCP Client credentials:
86 | - In the node settings, select **Connection Type**: `HTTP Streamable`
87 | - Create new credentials of type **MCP Client (HTTP Streamable) API**
88 | - Set **HTTP Streamable URL**: `http://localhost:3001/stream`
89 | - Add any required headers for authentication
90 |
91 | 3. Create a workflow using the MCP Client node:
92 | - Add an MCP Client node
93 | - Set the Connection Type to `HTTP Streamable`
94 | - Select your HTTP Streamable credentials
95 | - Execute the workflow to see the results
96 |
97 | ### Server-Sent Events (SSE) Transport (Deprecated, still available for legacy use)
98 |
99 | - **SSE URL**: The URL of the SSE endpoint (default: http://localhost:3001/sse)
100 | - **Messages Post Endpoint**: Optional custom endpoint for posting messages if different from the SSE URL
101 | - **Additional Headers**: Optional headers to send with requests (format: name:value, one per line)
102 |
103 | > **Deprecated:** SSE is deprecated and will not receive further updates, but remains available for legacy compatibility. For new projects, use HTTP Streamable.
104 |
105 | #### Example: Using a Local MCP Server with SSE (legacy)
106 |
107 | This example shows how to connect to a locally running MCP server using Server-Sent Events (SSE):
108 |
109 | 1. Start a local MCP server that supports SSE:
110 | ```bash
111 | npx @modelcontextprotocol/server-example-sse
112 | ```
113 |
114 | 2. Configure MCP Client credentials:
115 | - In the node settings, select **Connection Type**: `Server-Sent Events (SSE)`
116 | - Create new credentials of type **MCP Client (SSE) API**
117 | - Set **SSE URL**: `http://localhost:3001/sse`
118 | - Add any required headers for authentication
119 |
120 | 3. Create a workflow using the MCP Client node:
121 | - Add an MCP Client node
122 | - Set the Connection Type to `Server-Sent Events (SSE)`
123 | - Select your SSE credentials
124 | - Execute the workflow to see the results
125 |
126 | > **Note:** For new projects, HTTP Streamable is strongly recommended.
127 |
128 | ## Environment Variables
129 |
130 | The MCP Client node supports passing environment variables to MCP servers using the command-line based transport in two ways:
131 |
132 | ### 1. Using the Credentials UI
133 |
134 | You can add environment variables directly in the credentials configuration:
135 |
136 | 
137 |
138 | This method is useful for individual setups and testing. The values are stored securely as credentials in n8n.
139 |
140 | ### 2. Using Docker Environment Variables
141 |
142 | For Docker deployments, you can pass environment variables directly to your MCP servers by prefixing them with `MCP_`:
143 |
144 | ```yaml
145 | version: '3'
146 |
147 | services:
148 | n8n:
149 | image: n8nio/n8n
150 | environment:
151 | - MCP_BRAVE_API_KEY=your-api-key-here
152 | - MCP_OPENAI_API_KEY=your-openai-key-here
153 | - MCP_CUSTOM_SETTING=some-value
154 | # other configuration...
155 | ```
156 |
157 | These environment variables will be automatically passed to your MCP servers when they are executed.
158 |
159 | ### Example: Using Brave Search MCP Server
160 |
161 | This example shows how to set up and use the Brave Search MCP server:
162 |
163 | 1. Install the Brave Search MCP server:
164 | ```bash
165 | npm install -g @modelcontextprotocol/server-brave-search
166 | ```
167 |
168 | 2. Configure MCP Client credentials:
169 | - **Command**: `npx`
170 | - **Arguments**: `-y @modelcontextprotocol/server-brave-search`
171 | - **Environment Variables**: `BRAVE_API_KEY=your-api-key` Add a variables (space comma or newline separated)
172 |
173 | 3. Create a workflow that uses the MCP Client node:
174 | - Add an MCP Client node
175 | - Select the "List Tools" operation to see available search tools
176 | - Add another MCP Client node
177 | - Select the "Execute Tool" operation
178 | - Choose the "brave_search" tool
179 | - Set Parameters to: `{"query": "latest AI news"}`
180 |
181 | 
182 |
183 | The node will execute the search and return the results in the output.
184 |
185 | ### Example: Multi-Server Setup with AI Agent
186 |
187 | This example demonstrates how to set up multiple MCP servers in a production environment and use them with an AI agent:
188 |
189 | 1. Configure your docker-compose.yml file:
190 |
191 | ```yaml
192 | version: '3'
193 |
194 | services:
195 | n8n:
196 | image: n8nio/n8n
197 | environment:
198 | # MCP server environment variables
199 | - MCP_BRAVE_API_KEY=your-brave-api-key
200 | - MCP_OPENAI_API_KEY=your-openai-key
201 | - MCP_SERPER_API_KEY=your-serper-key
202 | - MCP_WEATHER_API_KEY=your-weather-api-key
203 |
204 | # Enable community nodes as tools
205 | - N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true
206 | ports:
207 | - "5678:5678"
208 | volumes:
209 | - ~/.n8n:/home/node/.n8n
210 | ```
211 |
212 | 2. Create multiple MCP Client credentials in n8n:
213 |
214 | **Brave Search Credentials**:
215 | - Command: `npx`
216 | - Arguments: `-y @modelcontextprotocol/server-brave-search`
217 |
218 | **OpenAI Tools Credentials**:
219 | - Command: `npx`
220 | - Arguments: `-y @modelcontextprotocol/server-openai`
221 |
222 | **Web Search Credentials**:
223 | - Command: `npx`
224 | - Arguments: `-y @modelcontextprotocol/server-serper`
225 |
226 | **Weather API Credentials**:
227 | - Command: `npx`
228 | - Arguments: `-y @modelcontextprotocol/server-weather`
229 |
230 | 3. Create an AI Agent workflow:
231 | - Add an AI Agent node
232 | - Enable MCP Client as a tool
233 | - Configure different MCP Client nodes with different credentials
234 | - Create a prompt that uses multiple data sources
235 |
236 | 
237 |
238 | Example AI Agent prompt:
239 | ```
240 | I need you to help me plan a trip. First, search for popular destinations in {destination_country}.
241 | Then, check the current weather in the top 3 cities.
242 | Finally, find some recent news about travel restrictions for these places.
243 | ```
244 |
245 | With this setup, the AI agent can use multiple MCP tools across different servers, all using environment variables configured in your Docker deployment.
246 |
247 | ## Operations
248 |
249 | The MCP Client node supports the following operations:
250 |
251 | 
252 |
253 | - **Execute Tool** - Execute a specific tool with parameters
254 | - **Get Prompt** - Get a specific prompt template
255 | - **List Prompts** - Get a list of available prompts
256 | - **List Resources** - Get a list of available resources from the MCP server
257 | - **List Tools** - Get a list of available tools
258 | - **Read Resource** - Read a specific resource by URI
259 |
260 | ### Example: List Tools Operation
261 |
262 | 
263 |
264 | The List Tools operation returns all available tools from the MCP server, including their names, descriptions, and parameter schemas.
265 |
266 | ### Example: Execute Tool Operation
267 |
268 | 
269 |
270 | The Execute Tool operation allows you to execute a specific tool with parameters. Make sure to select the tool you want to execute from the dropdown menu.
271 |
272 | ## Using as a Tool
273 |
274 | This node can be used as a tool in n8n AI Agents. To enable community nodes as tools, you need to set the `N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE` environment variable to `true`.
275 |
276 | ### Setting the Environment Variable
277 |
278 | **If you're using a bash/zsh shell:**
279 | ```bash
280 | export N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true
281 | n8n start
282 | ```
283 |
284 | **If you're using Docker:**
285 | Add to your docker-compose.yml file:
286 | ```yaml
287 | environment:
288 | - N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true
289 | ```
290 |
291 | **If you're using the desktop app:**
292 | Create a `.env` file in the n8n directory:
293 | ```
294 | N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true
295 | ```
296 |
297 | **If you want to set it permanently on Mac/Linux:**
298 | Add to your `~/.zshrc` or `~/.bash_profile`:
299 | ```bash
300 | export N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true
301 | ```
302 |
303 | Example of an AI Agent workflow results:
304 |
305 | 
306 |
307 | After setting this environment variable and restarting n8n, your MCP Client node will be available as a tool in AI Agent nodes.
308 |
309 | ## Compatibility
310 |
311 | - Requires n8n version 1.0.0 or later
312 | - Compatible with MCP Protocol version 1.0.0 or later
313 | - Supports both STDIO and SSE transports for connecting to MCP servers
314 | - SSE transport requires a server that implements the MCP Server-Sent Events specification
315 |
316 | ## Resources
317 |
318 | * [n8n community nodes documentation](https://docs.n8n.io/integrations/community-nodes/)
319 | * [Model Context Protocol Documentation](https://modelcontextprotocol.io/docs/)
320 | * [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
321 | * [MCP Transports Overview](https://modelcontextprotocol.io/docs/concepts/transports)
322 | * [Using SSE in MCP](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/client/sse.ts)
323 |
324 |
325 |
--------------------------------------------------------------------------------
/__tests__/McpClient.test.ts:
--------------------------------------------------------------------------------
1 | import { McpClient } from '../nodes/McpClient/McpClient.node';
2 |
3 | describe('McpClient Node', () => {
4 | let mcpClient: McpClient;
5 |
6 | beforeEach(() => {
7 | mcpClient = new McpClient();
8 | });
9 |
10 | it('should have the correct node type', () => {
11 | expect(mcpClient.description.name).toBe('mcpClient');
12 | });
13 |
14 | it('should have properties defined', () => {
15 | expect(mcpClient.description.properties).toBeDefined();
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/assets/brave-search-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nerding-io/n8n-nodes-mcp/03581616cdab8a61f9946d4cb4bedb5851e806b1/assets/brave-search-example.png
--------------------------------------------------------------------------------
/assets/credentials-envs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nerding-io/n8n-nodes-mcp/03581616cdab8a61f9946d4cb4bedb5851e806b1/assets/credentials-envs.png
--------------------------------------------------------------------------------
/assets/credentials.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nerding-io/n8n-nodes-mcp/03581616cdab8a61f9946d4cb4bedb5851e806b1/assets/credentials.png
--------------------------------------------------------------------------------
/assets/execute-tool-result.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nerding-io/n8n-nodes-mcp/03581616cdab8a61f9946d4cb4bedb5851e806b1/assets/execute-tool-result.png
--------------------------------------------------------------------------------
/assets/execute-tool.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nerding-io/n8n-nodes-mcp/03581616cdab8a61f9946d4cb4bedb5851e806b1/assets/execute-tool.png
--------------------------------------------------------------------------------
/assets/list-tools.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nerding-io/n8n-nodes-mcp/03581616cdab8a61f9946d4cb4bedb5851e806b1/assets/list-tools.png
--------------------------------------------------------------------------------
/assets/mcp-n8n.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nerding-io/n8n-nodes-mcp/03581616cdab8a61f9946d4cb4bedb5851e806b1/assets/mcp-n8n.png
--------------------------------------------------------------------------------
/assets/multi-server-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nerding-io/n8n-nodes-mcp/03581616cdab8a61f9946d4cb4bedb5851e806b1/assets/multi-server-example.png
--------------------------------------------------------------------------------
/assets/operations.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nerding-io/n8n-nodes-mcp/03581616cdab8a61f9946d4cb4bedb5851e806b1/assets/operations.png
--------------------------------------------------------------------------------
/assets/sse-credentials.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nerding-io/n8n-nodes-mcp/03581616cdab8a61f9946d4cb4bedb5851e806b1/assets/sse-credentials.png
--------------------------------------------------------------------------------
/assets/sse-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nerding-io/n8n-nodes-mcp/03581616cdab8a61f9946d4cb4bedb5851e806b1/assets/sse-example.png
--------------------------------------------------------------------------------
/credentials/McpClientApi.credentials.ts:
--------------------------------------------------------------------------------
1 | import { ICredentialType, INodeProperties } from 'n8n-workflow';
2 |
3 | export class McpClientApi implements ICredentialType {
4 | name = 'mcpClientApi';
5 | displayName = 'MCP Client (STDIO) API';
6 |
7 | // Cast the icon to the correct type for n8n
8 | icon = 'file:mcpClient.svg' as const;
9 |
10 | properties: INodeProperties[] = [
11 | {
12 | displayName: 'Command',
13 | name: 'command',
14 | type: 'string',
15 | default: '',
16 | required: true,
17 | description: 'Command to execute (e.g., npx @modelcontextprotocol/client, python script.py)',
18 | },
19 | {
20 | displayName: 'Arguments',
21 | name: 'args',
22 | type: 'string',
23 | default: '',
24 | description:
25 | 'Command line arguments (space-separated). Do not include API keys or sensitive information here - use Environments instead.',
26 | },
27 | {
28 | displayName: 'Environments',
29 | name: 'environments',
30 | type: 'string',
31 | default: '',
32 | typeOptions: {
33 | password: true,
34 | },
35 | description:
36 | 'Environment variables in NAME=VALUE format, separated by commas (e.g., BRAVE_API_KEY=xyz,OPENAI_API_KEY=abc)',
37 | },
38 | ];
39 | }
40 |
--------------------------------------------------------------------------------
/credentials/McpClientHttpApi.credentials.ts:
--------------------------------------------------------------------------------
1 | import { ICredentialType, INodeProperties } from 'n8n-workflow';
2 |
3 | export class McpClientHttpApi implements ICredentialType {
4 | name = 'mcpClientHttpApi';
5 | displayName = 'MCP Client (HTTP Streamable) API';
6 |
7 | // Cast the icon to the correct type for n8n
8 | icon = 'file:mcpClient.svg' as const;
9 |
10 | properties: INodeProperties[] = [
11 | {
12 | displayName: 'HTTP Stream URL',
13 | name: 'httpStreamUrl',
14 | type: 'string',
15 | default: 'http://localhost:3001/stream',
16 | required: true,
17 | description: 'URL of the HTTP stream endpoint for the MCP server',
18 | },
19 | {
20 | displayName: 'HTTP Connection Timeout',
21 | name: 'httpTimeout',
22 | type: 'number',
23 | default: 60000,
24 | required: false,
25 | description: 'Timeout for the HTTP stream connection in milliseconds. Default is 60 seconds.',
26 | },
27 | {
28 | displayName: 'Messages Post Endpoint',
29 | name: 'messagesPostEndpoint',
30 | type: 'string',
31 | default: '',
32 | description: 'Optional custom endpoint for posting messages (if different from HTTP stream URL)',
33 | },
34 | {
35 | displayName: 'Additional Headers',
36 | name: 'headers',
37 | type: 'string',
38 | default: '',
39 | description: 'Additional headers to send in the request in NAME=VALUE format, separated by commas (e.g., BRAVE_API_KEY=xyz,OPENAI_API_KEY=abc)',
40 | },
41 | ];
42 | }
--------------------------------------------------------------------------------
/credentials/McpClientSseApi.credentials.ts:
--------------------------------------------------------------------------------
1 | import { ICredentialType, INodeProperties } from 'n8n-workflow';
2 |
3 | export class McpClientSseApi implements ICredentialType {
4 | name = 'mcpClientSseApi';
5 | displayName = 'MCP Client (SSE) API';
6 |
7 | // Cast the icon to the correct type for n8n
8 | icon = 'file:mcpClient.svg' as const;
9 |
10 | properties: INodeProperties[] = [
11 | {
12 | displayName: 'SSE URL',
13 | name: 'sseUrl',
14 | type: 'string',
15 | default: 'http://localhost:3001/sse',
16 | required: true,
17 | description: 'URL of the SSE endpoint for the MCP server',
18 | },
19 | {
20 | displayName: 'SSE Connection Timeout',
21 | name: 'sseTimeout',
22 | type: 'number',
23 | default: 60000,
24 | required: false,
25 | description: 'Timeout for the SSE connection in milliseconds. Default is 60 seconds.',
26 | },
27 | {
28 | displayName: 'Messages Post Endpoint',
29 | name: 'messagesPostEndpoint',
30 | type: 'string',
31 | default: '',
32 | description: 'Optional custom endpoint for posting messages (if different from SSE URL)',
33 | },
34 | {
35 | displayName: 'Additional Headers',
36 | name: 'headers',
37 | type: 'string',
38 | default: '',
39 | description: 'Additional headers to send in the request in NAME=VALUE format, separated by commas (e.g., BRAVE_API_KEY=xyz,OPENAI_API_KEY=abc)',
40 | },
41 | ];
42 | }
43 |
--------------------------------------------------------------------------------
/credentials/mcpClient.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const { task, src, dest } = require('gulp');
3 |
4 | task('build:icons', copyIcons);
5 |
6 | function copyIcons() {
7 | const nodeSource = path.resolve('nodes', '**', '*.{png,svg}');
8 | const nodeDestination = path.resolve('dist', 'nodes');
9 |
10 | src(nodeSource).pipe(dest(nodeDestination));
11 |
12 | const credSource = path.resolve('credentials', '**', '*.{png,svg}');
13 | const credDestination = path.resolve('dist', 'credentials');
14 |
15 | return src(credSource).pipe(dest(credDestination));
16 | }
17 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | // This file ensures n8n can find and load your nodes and credentials
2 | const { McpClient } = require('./dist/nodes/McpClient/McpClient.node.js');
3 |
4 | module.exports = {
5 | nodeTypes: {
6 | mcpClient: McpClient,
7 | },
8 | credentialTypes: {
9 | mcpClientApi: require('./dist/credentials/McpClientApi.credentials.js').McpClientApi,
10 | mcpClientSseApi: require('./dist/credentials/McpClientSseApi.credentials.js').McpClientSseApi,
11 | mcpClientHttpApi: require('./dist/credentials/McpClientHttpApi.credentials.js').McpClientHttpApi,
12 | },
13 | };
14 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'ts-jest',
3 | testEnvironment: 'node',
4 | testMatch: ['**/__tests__/**/*.test.ts'],
5 | transform: {
6 | '^.+\\.tsx?$': [
7 | 'ts-jest',
8 | {
9 | tsconfig: 'tsconfig.json',
10 | },
11 | ],
12 | },
13 | moduleFileExtensions: ['ts', 'js', 'json'],
14 | };
15 |
--------------------------------------------------------------------------------
/nodes/McpClient/McpClient.node.test.ts:
--------------------------------------------------------------------------------
1 | // Mock a minimal McpClient class or relevant parts for testing the parsing logic.
2 | // The actual McpClient class has many dependencies that are not needed for these unit tests.
3 |
4 | // Helper function to simulate the core parsing logic for environment variables
5 | const parseEnvVars = (envString: string | undefined): Record => {
6 | const env: Record = {};
7 | if (envString) {
8 | const envLines = envString.split('\n');
9 | for (const line of envLines) {
10 | const equalsIndex = line.indexOf('=');
11 | if (equalsIndex > 0) { // Ensure '=' is present and not the first character
12 | const name = line.substring(0, equalsIndex).trim();
13 | const value = line.substring(equalsIndex + 1).trim();
14 | if (name && value !== undefined) {
15 | env[name] = value;
16 | }
17 | }
18 | }
19 | }
20 | return env;
21 | };
22 |
23 | // Helper function to simulate the core parsing logic for headers
24 | const parseHeaders = (headersString: string | undefined): Record => {
25 | const headers: Record = {};
26 | if (headersString) {
27 | const headerLines = headersString.split('\n');
28 | for (const line of headerLines) {
29 | const equalsIndex = line.indexOf('=');
30 | if (equalsIndex > 0) { // Ensure '=' is present and not the first character
31 | const name = line.substring(0, equalsIndex).trim();
32 | const value = line.substring(equalsIndex + 1).trim();
33 | if (name && value !== undefined) {
34 | headers[name] = value;
35 | }
36 | }
37 | }
38 | }
39 | return headers;
40 | };
41 |
42 | describe('McpClient Node - Credentials Parsing', () => {
43 | describe('cmdCredentials.environments parsing', () => {
44 | const testCases = [
45 | {
46 | name: 'Basic Test',
47 | input: 'FOO=bar\nBAZ=qux',
48 | expected: { FOO: 'bar', BAZ: 'qux' },
49 | },
50 | {
51 | name: 'Value with Spaces',
52 | input: 'MY_VAR=hello world',
53 | expected: { MY_VAR: 'hello world' },
54 | },
55 | {
56 | name: 'Value with Commas',
57 | input: 'LIST_OF_THINGS=apple,banana,orange',
58 | expected: { LIST_OF_THINGS: 'apple,banana,orange' },
59 | },
60 | {
61 | name: 'JSON Value',
62 | input: 'JSON_DATA={"key": "value", "number": 123, "nested": {"foo": "bar"}}',
63 | expected: { JSON_DATA: '{"key": "value", "number": 123, "nested": {"foo": "bar"}}' },
64 | },
65 | {
66 | name: 'Equals Sign in Value',
67 | input: 'FORMULA=E=mc^2',
68 | expected: { FORMULA: 'E=mc^2' },
69 | },
70 | {
71 | name: 'Whitespace Trimming',
72 | input: ' KEY_WITH_SPACES = value with spaces \nANOTHER=value',
73 | expected: { KEY_WITH_SPACES: 'value with spaces', ANOTHER: 'value' },
74 | },
75 | {
76 | name: 'Empty Lines and Lines without Equals',
77 | input: 'VALID=true\n\nINVALID_LINE\nANOTHER_VALID=false',
78 | expected: { VALID: 'true', ANOTHER_VALID: 'false' },
79 | },
80 | {
81 | name: 'Empty Input',
82 | input: '',
83 | expected: {},
84 | },
85 | {
86 | name: 'Line with only Equals Sign',
87 | input: '=',
88 | expected: {},
89 | },
90 | {
91 | name: 'Key starting with Equals',
92 | input: '=INVALID_KEY=value',
93 | expected: {},
94 | },
95 | {
96 | name: 'Value is just whitespace',
97 | input: 'KEY_WITH_WHITESPACE_VALUE= ',
98 | expected: { KEY_WITH_WHITESPACE_VALUE: '' },
99 | },
100 | {
101 | name: 'Value can be empty after key',
102 | input: 'EMPTY_VALUE=',
103 | expected: { EMPTY_VALUE: '' },
104 | },
105 | {
106 | name: 'Mixed valid and invalid lines',
107 | input: 'A=1\nB=\n C = 2 \nINVALID\n=startswithEquals\n D = 3 ',
108 | expected: { A: '1', B: '', C: '2', D: '3' },
109 | },
110 | ];
111 |
112 | testCases.forEach(tc => {
113 | it(`should correctly parse: ${tc.name}`, () => {
114 | // In a real test, you would mock this.getCredentials('mcpClientApi')
115 | // and then call a method that uses this parsing logic.
116 | // For this outline, we'll call the helper directly.
117 | const result = parseEnvVars(tc.input);
118 | expect(result).toEqual(tc.expected);
119 | });
120 | });
121 | });
122 |
123 | describe('httpCredentials.headers parsing', () => {
124 | const testCases = [
125 | {
126 | name: 'Basic Header',
127 | input: 'Content-Type=application/json\nAuthorization=Bearer token123',
128 | expected: { 'Content-Type': 'application/json', Authorization: 'Bearer token123' },
129 | },
130 | {
131 | name: 'Header Value with Spaces',
132 | input: 'X-Custom-Header=some value with spaces',
133 | expected: { 'X-Custom-Header': 'some value with spaces' },
134 | },
135 | {
136 | name: 'Header Value with Commas',
137 | input: 'Accept-Language=en-US,en;q=0.9,fr;q=0.8',
138 | expected: { 'Accept-Language': 'en-US,en;q=0.9,fr;q=0.8' },
139 | },
140 | {
141 | name: 'JSON in Header Value',
142 | input: 'X-Json-Data={"id": 1, "name": "test", "tags": ["a", "b"]}',
143 | expected: { 'X-Json-Data': '{"id": 1, "name": "test", "tags": ["a", "b"]}' },
144 | },
145 | {
146 | name: 'Equals Sign in Header Value',
147 | input: 'X-Query-Param=filter=some_value',
148 | expected: { 'X-Query-Param': 'filter=some_value' },
149 | },
150 | {
151 | name: 'Whitespace Trimming for Headers',
152 | input: ' X-Padded-Header = padded value \nUser-Agent=N8N',
153 | expected: { 'X-Padded-Header': 'padded value', 'User-Agent': 'N8N' },
154 | },
155 | {
156 | name: 'Empty Lines and Invalid Header Lines',
157 | input: 'Valid-Header=valid\n\nNotAValidHeaderLine\nAnother-Header=true',
158 | expected: { 'Valid-Header': 'valid', 'Another-Header': 'true' },
159 | },
160 | {
161 | name: 'Empty Input for Headers',
162 | input: '',
163 | expected: {},
164 | },
165 | {
166 | name: 'Header value can be empty',
167 | input: 'X-Empty-Value=',
168 | expected: { 'X-Empty-Value': ''}
169 | },
170 | {
171 | name: 'Mixed valid and invalid header lines',
172 | input: 'Header-One=value1\nHeader-Two=\n Invalid Line \n Header-Three = value3 ',
173 | expected: { 'Header-One': 'value1', 'Header-Two': '', 'Header-Three': 'value3' },
174 | },
175 | ];
176 |
177 | testCases.forEach(tc => {
178 | it(`should correctly parse: ${tc.name}`, () => {
179 | // Similar to above, this is a direct call to the helper for the outline.
180 | const result = parseHeaders(tc.input);
181 | expect(result).toEqual(tc.expected);
182 | });
183 | });
184 | });
185 |
186 | // sseCredentials.headers parsing uses the same logic as httpCredentials.headers
187 | // So, we can reuse the same test cases, just imagining they come from 'mcpClientSseApi'
188 | describe('sseCredentials.headers parsing', () => {
189 | const testCases = [
190 | {
191 | name: 'Basic Header (SSE)',
192 | input: 'Content-Type=application/json\nAuthorization=Bearer token456',
193 | expected: { 'Content-Type': 'application/json', Authorization: 'Bearer token456' },
194 | },
195 | {
196 | name: 'Header Value with Spaces (SSE)',
197 | input: 'X-Sse-Custom-Header=sse value with spaces',
198 | expected: { 'X-Sse-Custom-Header': 'sse value with spaces' },
199 | },
200 | {
201 | name: 'JSON in Header Value (SSE)',
202 | input: 'X-Sse-Json-Data={"event": "update", "data": {"value": 42}}',
203 | expected: { 'X-Sse-Json-Data': '{"event": "update", "data": {"value": 42}}' },
204 | },
205 | {
206 | name: 'Empty Input for Headers (SSE)',
207 | input: '',
208 | expected: {},
209 | },
210 | {
211 | name: 'Header value can be empty (SSE)',
212 | input: 'X-Sse-Empty-Value=',
213 | expected: { 'X-Sse-Empty-Value': ''}
214 | },
215 | ];
216 |
217 | testCases.forEach(tc => {
218 | it(`should correctly parse: ${tc.name}`, () => {
219 | // Direct call for the outline.
220 | const result = parseHeaders(tc.input);
221 | expect(result).toEqual(tc.expected);
222 | });
223 | });
224 | });
225 | });
226 |
--------------------------------------------------------------------------------
/nodes/McpClient/McpClient.node.ts:
--------------------------------------------------------------------------------
1 | import {
2 | IExecuteFunctions,
3 | INodeExecutionData,
4 | INodeType,
5 | INodeTypeDescription,
6 | NodeConnectionType,
7 | NodeOperationError,
8 | } from 'n8n-workflow';
9 | import { DynamicStructuredTool } from '@langchain/core/tools';
10 | import { z } from 'zod';
11 | import { zodToJsonSchema } from 'zod-to-json-schema';
12 | import { Client } from '@modelcontextprotocol/sdk/client/index.js';
13 | import { RequestOptions } from '@modelcontextprotocol/sdk/shared/protocol';
14 | import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
15 | import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
16 | import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
17 |
18 | // Add Node.js process type declaration
19 | declare const process: {
20 | env: Record;
21 | };
22 |
23 | export class McpClient implements INodeType {
24 | description: INodeTypeDescription = {
25 | displayName: 'MCP Client',
26 | name: 'mcpClient',
27 | icon: 'file:mcpClient.svg',
28 | group: ['transform'],
29 | version: 1,
30 | subtitle: '={{$parameter["operation"]}}',
31 | description: 'Use MCP client',
32 | defaults: {
33 | name: 'MCP Client',
34 | },
35 | // @ts-ignore - node-class-description-outputs-wrong
36 | inputs: [{ type: NodeConnectionType.Main }],
37 | // @ts-ignore - node-class-description-outputs-wrong
38 | outputs: [{ type: NodeConnectionType.Main }],
39 | usableAsTool: true,
40 | credentials: [
41 | {
42 | name: 'mcpClientApi',
43 | required: false,
44 | displayOptions: {
45 | show: {
46 | connectionType: ['cmd'],
47 | },
48 | },
49 | },
50 | {
51 | name: 'mcpClientSseApi',
52 | required: false,
53 | displayOptions: {
54 | show: {
55 | connectionType: ['sse'],
56 | },
57 | },
58 | },
59 | {
60 | name: 'mcpClientHttpApi',
61 | required: false,
62 | displayOptions: {
63 | show: {
64 | connectionType: ['http'],
65 | },
66 | },
67 | },
68 | ],
69 | properties: [
70 | {
71 | displayName: 'Connection Type',
72 | name: 'connectionType',
73 | type: 'options',
74 | options: [
75 | {
76 | name: 'Command Line (STDIO)',
77 | value: 'cmd',
78 | },
79 | {
80 | name: 'Server-Sent Events (SSE)',
81 | value: 'sse',
82 | description: 'Deprecated: Use HTTP Streamable instead',
83 | },
84 | {
85 | name: 'HTTP Streamable',
86 | value: 'http',
87 | description: 'Use HTTP streamable protocol for real-time communication',
88 | },
89 | ],
90 | default: 'cmd',
91 | description: 'Choose the transport type to connect to MCP server',
92 | },
93 | {
94 | displayName: 'Operation',
95 | name: 'operation',
96 | type: 'options',
97 | noDataExpression: true,
98 | options: [
99 | {
100 | name: 'Execute Tool',
101 | value: 'executeTool',
102 | description: 'Execute a specific tool',
103 | action: 'Execute a tool',
104 | },
105 | {
106 | name: 'Get Prompt',
107 | value: 'getPrompt',
108 | description: 'Get a specific prompt template',
109 | action: 'Get a prompt template',
110 | },
111 | {
112 | name: 'List Prompts',
113 | value: 'listPrompts',
114 | description: 'Get available prompts',
115 | action: 'List available prompts',
116 | },
117 | {
118 | name: 'List Resource Templates',
119 | value: 'listResourceTemplates',
120 | description: 'Get a list of available resource templates',
121 | action: 'List available resource templates',
122 | },
123 | {
124 | name: 'List Resources',
125 | value: 'listResources',
126 | description: 'Get a list of available resources',
127 | action: 'List available resources',
128 | },
129 | {
130 | name: 'List Tools',
131 | value: 'listTools',
132 | description: 'Get available tools',
133 | action: 'List available tools',
134 | },
135 | {
136 | name: 'Read Resource',
137 | value: 'readResource',
138 | description: 'Read a specific resource by URI',
139 | action: 'Read a resource',
140 | },
141 | ],
142 | default: 'listTools',
143 | required: true,
144 | },
145 | {
146 | displayName: 'Resource URI',
147 | name: 'resourceUri',
148 | type: 'string',
149 | required: true,
150 | displayOptions: {
151 | show: {
152 | operation: ['readResource'],
153 | },
154 | },
155 | default: '',
156 | description: 'URI of the resource to read',
157 | },
158 | {
159 | displayName: 'Tool Name',
160 | name: 'toolName',
161 | type: 'string',
162 | required: true,
163 | displayOptions: {
164 | show: {
165 | operation: ['executeTool'],
166 | },
167 | },
168 | default: '',
169 | description: 'Name of the tool to execute',
170 | },
171 | {
172 | displayName: 'Tool Parameters',
173 | name: 'toolParameters',
174 | type: 'json',
175 | required: true,
176 | displayOptions: {
177 | show: {
178 | operation: ['executeTool'],
179 | },
180 | },
181 | default: '{}',
182 | description: 'Parameters to pass to the tool in JSON format',
183 | },
184 | {
185 | displayName: 'Prompt Name',
186 | name: 'promptName',
187 | type: 'string',
188 | required: true,
189 | displayOptions: {
190 | show: {
191 | operation: ['getPrompt'],
192 | },
193 | },
194 | default: '',
195 | description: 'Name of the prompt template to get',
196 | },
197 | ],
198 | };
199 |
200 | async execute(this: IExecuteFunctions): Promise {
201 | const returnData: INodeExecutionData[] = [];
202 | const operation = this.getNodeParameter('operation', 0) as string;
203 | let transport: Transport | undefined;
204 |
205 | // For backward compatibility - if connectionType isn't set, default to 'cmd'
206 | let connectionType = 'cmd';
207 | try {
208 | connectionType = this.getNodeParameter('connectionType', 0) as string;
209 | } catch (error) {
210 | // If connectionType parameter doesn't exist, keep default 'cmd'
211 | this.logger.debug('ConnectionType parameter not found, using default "cmd" transport');
212 | }
213 | let timeout = 600000;
214 |
215 | try {
216 | if (connectionType === 'http') {
217 | // Use HTTP Streamable transport
218 | const httpCredentials = await this.getCredentials('mcpClientHttpApi');
219 |
220 | // Dynamically import the HTTP client
221 | const { StreamableHTTPClientTransport } = await import('@modelcontextprotocol/sdk/client/streamableHttp.js');
222 |
223 | const httpStreamUrl = httpCredentials.httpStreamUrl as string;
224 | const messagesPostEndpoint = (httpCredentials.messagesPostEndpoint as string) || '';
225 | timeout = httpCredentials.httpTimeout as number || 60000;
226 |
227 | // Parse headers
228 | let headers: Record = {};
229 | if (httpCredentials.headers) {
230 | const headerLines = (httpCredentials.headers as string).split('\n');
231 | for (const line of headerLines) {
232 | const equalsIndex = line.indexOf('=');
233 | // Ensure '=' is present and not the first character of the line
234 | if (equalsIndex > 0) {
235 | const name = line.substring(0, equalsIndex).trim();
236 | const value = line.substring(equalsIndex + 1).trim();
237 | // Add to headers object if key is not empty and value is defined
238 | if (name && value !== undefined) {
239 | headers[name] = value;
240 | }
241 | }
242 | }
243 | }
244 |
245 | const requestInit: RequestInit = { headers };
246 | if (messagesPostEndpoint) {
247 | (requestInit as any).endpoint = new URL(messagesPostEndpoint);
248 | }
249 |
250 | transport = new StreamableHTTPClientTransport(
251 | new URL(httpStreamUrl),
252 | { requestInit }
253 | );
254 | } else if (connectionType === 'sse') {
255 | // Use SSE transport
256 | const sseCredentials = await this.getCredentials('mcpClientSseApi');
257 |
258 | // Dynamically import the SSE client to avoid TypeScript errors
259 | const { SSEClientTransport } = await import('@modelcontextprotocol/sdk/client/sse.js');
260 |
261 | const sseUrl = sseCredentials.sseUrl as string;
262 | const messagesPostEndpoint = (sseCredentials.messagesPostEndpoint as string) || '';
263 | timeout = sseCredentials.sseTimeout as number || 60000;
264 |
265 | // Parse headers
266 | let headers: Record = {};
267 | if (sseCredentials.headers) {
268 | const headerLines = (sseCredentials.headers as string).split('\n');
269 | for (const line of headerLines) {
270 | const equalsIndex = line.indexOf('=');
271 | // Ensure '=' is present and not the first character of the line
272 | if (equalsIndex > 0) {
273 | const name = line.substring(0, equalsIndex).trim();
274 | const value = line.substring(equalsIndex + 1).trim();
275 | // Add to headers object if key is not empty and value is defined
276 | if (name && value !== undefined) {
277 | headers[name] = value;
278 | }
279 | }
280 | }
281 | }
282 |
283 | // Create SSE transport with dynamic import to avoid TypeScript errors
284 | transport = new SSEClientTransport(
285 | // @ts-ignore
286 | new URL(sseUrl),
287 | {
288 | // @ts-ignore
289 | eventSourceInit: { headers },
290 | // @ts-ignore
291 | requestInit: {
292 | headers,
293 | ...(messagesPostEndpoint
294 | ? {
295 | // @ts-ignore
296 | endpoint: new URL(messagesPostEndpoint),
297 | }
298 | : {}),
299 | },
300 | },
301 | );
302 |
303 | this.logger.debug(`Created SSE transport for MCP client URL: ${sseUrl}`);
304 | if (messagesPostEndpoint) {
305 | this.logger.debug(`Using custom POST endpoint: ${messagesPostEndpoint}`);
306 | }
307 | } else {
308 | // Use stdio transport (default)
309 | const cmdCredentials = await this.getCredentials('mcpClientApi');
310 |
311 | // Build environment variables object for MCP servers
312 | const env: Record = {
313 | // Preserve the PATH environment variable to ensure commands can be found
314 | PATH: process.env.PATH || '',
315 | };
316 |
317 | this.logger.debug(`Original PATH: ${process.env.PATH}`);
318 |
319 | // Parse newline-separated environment variables from credentials
320 | if (cmdCredentials.environments) {
321 | const envLines = (cmdCredentials.environments as string).split('\n');
322 | for (const line of envLines) {
323 | const equalsIndex = line.indexOf('=');
324 | // Ensure '=' is present and not the first character of the line
325 | if (equalsIndex > 0) {
326 | const name = line.substring(0, equalsIndex).trim();
327 | const value = line.substring(equalsIndex + 1).trim();
328 | // Add to env object if key is not empty and value is defined
329 | if (name && value !== undefined) {
330 | env[name] = value;
331 | }
332 | }
333 | }
334 | }
335 |
336 | // Process environment variables from Node.js
337 | // This allows Docker environment variables to override credentials
338 | for (const key in process.env) {
339 | // Only pass through MCP-related environment variables
340 | if (key.startsWith('MCP_') && process.env[key]) {
341 | // Strip off the MCP_ prefix when passing to the MCP server
342 | const envName = key.substring(4); // Remove 'MCP_'
343 | env[envName] = process.env[key] as string;
344 | }
345 | }
346 |
347 | transport = new StdioClientTransport({
348 | command: cmdCredentials.command as string,
349 | args: (cmdCredentials.args as string)?.split(' ') || [],
350 | env: env, // Always pass the env with PATH preserved
351 | });
352 |
353 | // Use n8n's logger instead of console.log
354 | this.logger.debug(
355 | `Transport created for MCP client command: ${cmdCredentials.command}, PATH: ${env.PATH}`,
356 | );
357 | }
358 |
359 | // Add error handling to transport
360 | if (transport) {
361 | transport.onerror = (error: Error) => {
362 | throw new NodeOperationError(this.getNode(), `Transport error: ${error.message}`);
363 | };
364 | }
365 |
366 | const client = new Client(
367 | {
368 | name: `${McpClient.name}-client`,
369 | version: '1.0.0',
370 | },
371 | {
372 | capabilities: {
373 | prompts: {},
374 | resources: {},
375 | tools: {},
376 | },
377 | },
378 | );
379 |
380 | try {
381 | if (!transport) {
382 | throw new NodeOperationError(this.getNode(), 'No transport available');
383 | }
384 | await client.connect(transport);
385 | this.logger.debug('Client connected to MCP server');
386 | } catch (connectionError) {
387 | this.logger.error(`MCP client connection error: ${(connectionError as Error).message}`);
388 | throw new NodeOperationError(
389 | this.getNode(),
390 | `Failed to connect to MCP server: ${(connectionError as Error).message}`,
391 | );
392 | }
393 |
394 | // Create a RequestOptions object from environment variables
395 | const requestOptions: RequestOptions = {};
396 | requestOptions.timeout = timeout;
397 |
398 | switch (operation) {
399 | case 'listResources': {
400 | const resources = await client.listResources();
401 | returnData.push({
402 | json: { resources },
403 | });
404 | break;
405 | }
406 |
407 | case 'listResourceTemplates': {
408 | const resourceTemplates = await client.listResourceTemplates();
409 | returnData.push({
410 | json: { resourceTemplates },
411 | });
412 | break;
413 | }
414 |
415 | case 'readResource': {
416 | const uri = this.getNodeParameter('resourceUri', 0) as string;
417 | const resource = await client.readResource({
418 | uri,
419 | });
420 | returnData.push({
421 | json: { resource },
422 | });
423 | break;
424 | }
425 |
426 | case 'listTools': {
427 | const rawTools = await client.listTools();
428 | const tools = Array.isArray(rawTools)
429 | ? rawTools
430 | : Array.isArray(rawTools?.tools)
431 | ? rawTools.tools
432 | : typeof rawTools?.tools === 'object' && rawTools.tools !== null
433 | ? Object.values(rawTools.tools)
434 | : [];
435 |
436 | if (!tools.length) {
437 | this.logger.warn('No tools found from MCP client response.');
438 | throw new NodeOperationError(this.getNode(), 'No tools found from MCP client');
439 | }
440 |
441 | const aiTools = tools.map((tool: any) => {
442 | const paramSchema = tool.inputSchema?.properties
443 | ? z.object(
444 | Object.entries(tool.inputSchema.properties).reduce(
445 | (acc: any, [key, prop]: [string, any]) => {
446 | let zodType: z.ZodType;
447 |
448 | switch (prop.type) {
449 | case 'string':
450 | zodType = z.string();
451 | break;
452 | case 'number':
453 | zodType = z.number();
454 | break;
455 | case 'integer':
456 | zodType = z.number().int();
457 | break;
458 | case 'boolean':
459 | zodType = z.boolean();
460 | break;
461 | case 'array':
462 | if (prop.items?.type === 'string') {
463 | zodType = z.array(z.string());
464 | } else if (prop.items?.type === 'number') {
465 | zodType = z.array(z.number());
466 | } else if (prop.items?.type === 'boolean') {
467 | zodType = z.array(z.boolean());
468 | } else {
469 | zodType = z.array(z.any());
470 | }
471 | break;
472 | case 'object':
473 | zodType = z.record(z.string(), z.any());
474 | break;
475 | default:
476 | zodType = z.any();
477 | }
478 |
479 | if (prop.description) {
480 | zodType = zodType.describe(prop.description);
481 | }
482 |
483 | if (!tool.inputSchema?.required?.includes(key)) {
484 | zodType = zodType.optional();
485 | }
486 |
487 | return {
488 | ...acc,
489 | [key]: zodType,
490 | };
491 | },
492 | {},
493 | ),
494 | )
495 | : z.object({});
496 |
497 | return new DynamicStructuredTool({
498 | name: tool.name,
499 | description: tool.description || `Execute the ${tool.name} tool`,
500 | schema: paramSchema,
501 | func: async (params) => {
502 | try {
503 | const result = await client.callTool({
504 | name: tool.name,
505 | arguments: params,
506 | }, CallToolResultSchema, requestOptions);
507 |
508 | return typeof result === 'object' ? JSON.stringify(result) : String(result);
509 | } catch (error) {
510 | throw new NodeOperationError(
511 | this.getNode(),
512 | `Failed to execute ${tool.name}: ${(error as Error).message}`,
513 | );
514 | }
515 | },
516 | });
517 | });
518 |
519 | returnData.push({
520 | json: {
521 | tools: aiTools.map((t: DynamicStructuredTool) => ({
522 | name: t.name,
523 | description: t.description,
524 | schema: zodToJsonSchema(t.schema as z.ZodTypeAny || z.object({})),
525 | })),
526 | },
527 | });
528 | break;
529 | }
530 |
531 | case 'executeTool': {
532 | const toolName = this.getNodeParameter('toolName', 0) as string;
533 | let toolParams;
534 |
535 | try {
536 | const rawParams = this.getNodeParameter('toolParameters', 0);
537 | this.logger.debug(`Raw tool parameters: ${JSON.stringify(rawParams)}`);
538 |
539 | // Handle different parameter types
540 | if (rawParams === undefined || rawParams === null) {
541 | // Handle null/undefined case
542 | toolParams = {};
543 | } else if (typeof rawParams === 'string') {
544 | // Handle string input (typical direct node usage)
545 | if (!rawParams || rawParams.trim() === '') {
546 | toolParams = {};
547 | } else {
548 | toolParams = JSON.parse(rawParams);
549 | }
550 | } else if (typeof rawParams === 'object') {
551 | // Handle object input (when used as a tool in AI Agent)
552 | toolParams = rawParams;
553 | } else {
554 | // Try to convert other types to object
555 | try {
556 | toolParams = JSON.parse(JSON.stringify(rawParams));
557 | } catch (parseError) {
558 | throw new NodeOperationError(
559 | this.getNode(),
560 | `Invalid parameter type: ${typeof rawParams}`,
561 | );
562 | }
563 | }
564 |
565 | // Ensure toolParams is an object
566 | if (
567 | typeof toolParams !== 'object' ||
568 | toolParams === null ||
569 | Array.isArray(toolParams)
570 | ) {
571 | throw new NodeOperationError(this.getNode(), 'Tool parameters must be a JSON object');
572 | }
573 | } catch (error) {
574 | throw new NodeOperationError(
575 | this.getNode(),
576 | `Failed to parse tool parameters: ${(error as Error).message
577 | }. Make sure the parameters are valid JSON.`,
578 | );
579 | }
580 |
581 | // Validate tool exists before executing
582 | try {
583 | const availableTools = await client.listTools();
584 | const toolsList = Array.isArray(availableTools)
585 | ? availableTools
586 | : Array.isArray(availableTools?.tools)
587 | ? availableTools.tools
588 | : Object.values(availableTools?.tools || {});
589 |
590 | const toolExists = toolsList.some((tool: any) => tool.name === toolName);
591 |
592 | if (!toolExists) {
593 | const availableToolNames = toolsList.map((t: any) => t.name).join(', ');
594 | throw new NodeOperationError(
595 | this.getNode(),
596 | `Tool '${toolName}' does not exist. Available tools: ${availableToolNames}`,
597 | );
598 | }
599 |
600 | this.logger.debug(
601 | `Executing tool: ${toolName} with params: ${JSON.stringify(toolParams)}`,
602 | );
603 |
604 | const result = await client.callTool({
605 | name: toolName,
606 | arguments: toolParams,
607 | }, CallToolResultSchema, requestOptions);
608 |
609 | this.logger.debug(`Tool executed successfully: ${JSON.stringify(result)}`);
610 |
611 | returnData.push({
612 | json: { result },
613 | });
614 | } catch (error) {
615 | throw new NodeOperationError(
616 | this.getNode(),
617 | `Failed to execute tool '${toolName}': ${(error as Error).message}`,
618 | );
619 | }
620 | break;
621 | }
622 |
623 | case 'listPrompts': {
624 | const prompts = await client.listPrompts();
625 | returnData.push({
626 | json: { prompts },
627 | });
628 | break;
629 | }
630 |
631 | case 'getPrompt': {
632 | const promptName = this.getNodeParameter('promptName', 0) as string;
633 | const prompt = await client.getPrompt({
634 | name: promptName,
635 | });
636 | returnData.push({
637 | json: { prompt },
638 | });
639 | break;
640 | }
641 |
642 | default:
643 | throw new NodeOperationError(this.getNode(), `Operation ${operation} not supported`);
644 | }
645 |
646 | return [returnData];
647 | } catch (error) {
648 | throw new NodeOperationError(
649 | this.getNode(),
650 | `Failed to execute operation: ${(error as Error).message}`,
651 | );
652 | } finally {
653 | if (transport) {
654 | await transport.close();
655 | }
656 | }
657 | }
658 | }
659 |
--------------------------------------------------------------------------------
/nodes/McpClient/mcpClient.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "n8n-nodes-mcp",
3 | "version": "0.1.28",
4 | "description": "MCP nodes for n8n ",
5 | "keywords": [
6 | "n8n-community-node-package",
7 | "mcp",
8 | "mcp-client",
9 | "mcp-client-node",
10 | "mcp-client-n8n",
11 | "mcp-client-n8n-node",
12 | "mcp-client-n8n-node-package"
13 | ],
14 | "license": "MIT",
15 | "homepage": "",
16 | "author": {
17 | "name": "Jd Fiscus",
18 | "email": "jd@nerding.io"
19 | },
20 | "repository": {
21 | "type": "git",
22 | "url": "https://github.com/nerding-io/n8n-nodes-mcp.git"
23 | },
24 | "main": "index.js",
25 | "scripts": {
26 | "build": "tsc && gulp build:icons",
27 | "dev": "tsc --watch",
28 | "format": "prettier nodes credentials --write",
29 | "lint": "eslint nodes credentials package.json",
30 | "lintfix": "eslint nodes credentials package.json --fix",
31 | "prepublishOnly": "npm run build && npm run lint -c .eslintrc.prepublish.js nodes credentials package.json",
32 | "test": "jest"
33 | },
34 | "files": [
35 | "dist"
36 | ],
37 | "n8n": {
38 | "n8nNodesApiVersion": 1,
39 | "credentials": [
40 | "dist/credentials/McpClientApi.credentials.js",
41 | "dist/credentials/McpClientSseApi.credentials.js",
42 | "dist/credentials/McpClientHttpApi.credentials.js"
43 | ],
44 | "nodes": [
45 | "dist/nodes/McpClient/McpClient.node.js"
46 | ]
47 | },
48 | "devDependencies": {
49 | "@types/jest": "^29.5.14",
50 | "@types/node": "^22.15.21",
51 | "@typescript-eslint/parser": "~5.45",
52 | "eslint": "^8.57.1",
53 | "eslint-plugin-n8n-nodes-base": "^1.11.0",
54 | "gulp": "^4.0.2",
55 | "jest": "^29.7.0",
56 | "n8n-workflow": "*",
57 | "prettier": "^2.7.1",
58 | "ts-jest": "^29.2.6",
59 | "typescript": "~4.8.4"
60 | },
61 | "dependencies": {
62 | "@langchain/core": "^0.3.43",
63 | "@modelcontextprotocol/sdk": "^1.10.2",
64 | "zod": "^3.24.0",
65 | "zod-to-json-schema": "^3.24.0"
66 | },
67 | "overrides": {
68 | "pkce-challenge": "3.0.0"
69 | },
70 | "peerDependencies": {
71 | "n8n-workflow": "*"
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "target": "es2019",
7 | "lib": ["es2019", "es2020", "es2022.error"],
8 | "removeComments": true,
9 | "useUnknownInCatchVariables": false,
10 | "forceConsistentCasingInFileNames": true,
11 | "noImplicitAny": true,
12 | "noImplicitReturns": true,
13 | "noUnusedLocals": true,
14 | "strictNullChecks": true,
15 | "preserveConstEnums": true,
16 | "esModuleInterop": true,
17 | "resolveJsonModule": true,
18 | "incremental": true,
19 | "declaration": true,
20 | "sourceMap": true,
21 | "skipLibCheck": true,
22 | "outDir": "./dist/",
23 | "types": ["node", "jest"]
24 | },
25 | "include": [
26 | "credentials/**/*",
27 | "nodes/**/*",
28 | "nodes/**/*.json",
29 | "package.json",
30 | "__tests__/**/*"
31 | ],
32 | }
33 |
--------------------------------------------------------------------------------