Compare commits

..

7 Commits

Author SHA1 Message Date
Romuald Członkowski
ce2c94c1a5 fix: recognize dynamic AI Tool nodes in validator (Issue #522) (#526)
When n8n connects any node to an AI Agent's tool slot, it creates a
dynamic Tool variant at runtime (e.g., googleDrive → googleDriveTool).
These don't exist in npm packages, causing false "unknown node type"
errors.

Added validation-time inference: when a *Tool node type is not found,
check if the base node exists. If yes, treat as valid with warning.

Changes:
- workflow-validator.ts: Add INFERRED_TOOL_VARIANT logic
- node-similarity-service.ts: Add 98% confidence for valid Tool variants
- Added 7 unit tests for inferred tool variant functionality

Fixes #522

Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Romuald Członkowski <romualdczlonkowski@MacBook-Pro-Romuald.local>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 18:09:55 +01:00
Romuald Członkowski
861005eeed fix: deprecate USE_FIXED_HTTP for SSE streaming support (Issue #524) (#525)
* fix: deprecate USE_FIXED_HTTP for SSE streaming support (Issue #524)

The fixed HTTP implementation does not support SSE streaming required
by clients like OpenAI Codex. This commit deprecates USE_FIXED_HTTP
and makes SingleSessionHTTPServer the default.

Changes:
- Add deprecation warnings in src/mcp/index.ts and src/http-server.ts
- Remove USE_FIXED_HTTP from docker-compose.yml and Dockerfile.railway
- Update .env.example with deprecation notice
- Rename npm script to start:http:fixed:deprecated
- Update all documentation to remove USE_FIXED_HTTP references
- Mark test case as deprecated

Users should unset USE_FIXED_HTTP to use the modern SingleSessionHTTPServer
which supports both JSON-RPC and SSE streaming.

Closes #524

Concieved by Romuald Członkowski - www.aiadvisors.pl/en

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore: bump version to 2.31.8 and add CHANGELOG entry

- Fix comment inaccuracy: "deprecated" not "deprecated and removed"
- Bump version from 2.31.7 to 2.31.8
- Add CHANGELOG entry documenting USE_FIXED_HTTP deprecation
- Update all deprecation messages to reference v2.31.8

Concieved by Romuald Członkowski - www.aiadvisors.pl/en

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Romuald Członkowski <romualdczlonkowski@MacBook-Pro-Romuald.local>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 13:42:16 +01:00
Romuald Członkowski
7b0ff990ec chore: update n8n to 2.2.3 and bump version to 2.31.7 (#523)
- Updated n8n from 2.1.5 to 2.2.3
- Updated n8n-core from 2.1.4 to 2.2.2
- Updated n8n-workflow from 2.1.1 to 2.2.2
- Updated @n8n/n8n-nodes-langchain from 2.1.4 to 2.2.2
- Rebuilt node database with 540 nodes (434 from n8n-nodes-base, 106 from @n8n/n8n-nodes-langchain)
- Updated README badge with new n8n version
- Updated CHANGELOG with dependency changes

Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Romuald Członkowski <romualdczlonkowski@MacBook-Pro-Romuald.local>
Co-authored-by: Claude <noreply@anthropic.com>
2026-01-06 13:18:56 +01:00
Romuald Członkowski
25cb8bb455 chore: update n8n to 2.1.5 and bump version to 2.31.6 (#521)
- Updated n8n from 2.1.4 to 2.1.5
- Updated n8n-core from 2.1.3 to 2.1.4
- Updated @n8n/n8n-nodes-langchain from 2.1.3 to 2.1.4
- Rebuilt node database with 540 nodes (434 from n8n-nodes-base, 106 from @n8n/n8n-nodes-langchain)
- Updated README badge with new n8n version
- Updated CHANGELOG with dependency changes

Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Romuald Członkowski <romualdczlonkowski@MacBook-Pro-Romuald.local>
Co-authored-by: Claude <noreply@anthropic.com>
2026-01-04 10:43:35 +01:00
Bryan Thompson
2713db6d10 feat: add MCP tool annotations to all 20 tools (#512)
* feat: add MCP tool annotations to all 20 tools

Add MCP tool annotations per specification to help AI assistants
understand tool behavior and capabilities.

Documentation tools (7):
- tools_documentation, search_nodes, get_node, validate_node,
  get_template, search_templates, validate_workflow
- All marked readOnlyHint=true (local database queries)

Management tools (13):
- n8n_create_workflow, n8n_get_workflow, n8n_update_full_workflow,
  n8n_update_partial_workflow, n8n_delete_workflow, n8n_list_workflows,
  n8n_validate_workflow, n8n_autofix_workflow, n8n_test_workflow,
  n8n_executions, n8n_health_check, n8n_workflow_versions,
  n8n_deploy_template
- All marked openWorldHint=true (n8n API access)
- Destructive operations (delete_workflow, executions delete,
  workflow_versions delete/truncate) marked destructiveHint=true

Annotations follow MCP spec:
https://spec.modelcontextprotocol.io/specification/2025-03-26/server/tools/#annotations

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat: add idempotentHint to all read-only tools

Adds idempotentHint: true annotation to all read-only tools that produce
the same output when called multiple times:
- 7 documentation tools (tools.ts)
- 4 management tools (tools-n8n-manager.ts): n8n_get_workflow,
  n8n_list_workflows, n8n_validate_workflow, n8n_health_check

Also adds trailing newline to tools-n8n-manager.ts.

Conceived by Romuald Członkowski - www.aiadvisors.pl/en

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat: add idempotentHint to update operations, bump to 2.31.5

Adds idempotentHint: true to update operations that produce the same
result when called repeatedly with the same arguments:
- n8n_update_full_workflow
- n8n_update_partial_workflow
- n8n_autofix_workflow

Also bumps version to 2.31.5 and updates CHANGELOG.md with complete
documentation of all MCP tool annotations added in this PR.

Conceived by Romuald Członkowski - www.aiadvisors.pl/en

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: triepod-ai <noreply@github.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Romuald Członkowski <romualdczlonkowski@MacBook-Pro-Romuald.local>
2026-01-02 15:48:47 +01:00
Romuald Członkowski
f10772a9d2 fix: preserve workflow data during serialization (Issue #517) (#519)
Fixed a critical bug where workflow mutation data was corrupted during
serialization to Supabase. The recursive toSnakeCase() function was
converting nested workflow data, mangling:
- Connection keys (node names like "Webhook" → "_webhook")
- Node field names (typeVersion → type_version)

Solution: Replace recursive conversion with selective mutationToSupabaseFormat()
that only converts top-level field names to snake_case while preserving
nested workflow data exactly as-is.

Impact:
- 98.9% of workflow mutations had corrupted data
- Deployability rate improved from ~21% to ~68%

Changes:
- src/telemetry/batch-processor.ts: New selective converter
- tests/unit/telemetry/batch-processor.test.ts: 3 new regression tests

Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Romuald Członkowski <romualdczlonkowski@MacBook-Pro-Romuald.local>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 10:44:13 +01:00
Romuald Członkowski
808088f25e docs: fix connection keys documentation to say "node names" not "node IDs" (#510) (#511)
The documentation incorrectly stated connection keys should be "node IDs"
when n8n actually requires "node names". This caused workflow creation
failures for AI-generated workflows.

Changes:
- tools-n8n-manager.ts: "Keys are source node names (the name field, not id)"
- n8n-create-workflow.ts: "Keys are source node names (not IDs)"
- Fixed example: "Webhook"/"Slack" instead of "webhook_1"/"slack_1"
- get-template.ts: clarified "source node names"

Closes #510

Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Romuald Członkowski <romualdczlonkowski@MacBook-Pro-Romuald.local>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-26 14:50:58 +01:00
46 changed files with 1434 additions and 7116 deletions

View File

@@ -37,9 +37,11 @@ MCP_SERVER_HOST=localhost
# Server mode: stdio (local) or http (remote)
MCP_MODE=stdio
# Use fixed HTTP implementation (recommended for stability)
# Set to true to bypass StreamableHTTPServerTransport issues
USE_FIXED_HTTP=true
# DEPRECATED: USE_FIXED_HTTP is deprecated as of v2.31.8
# The fixed HTTP implementation does not support SSE streaming required by
# clients like OpenAI Codex. Use the default SingleSessionHTTPServer instead.
# See: https://github.com/czlonkowski/n8n-mcp/issues/524
# USE_FIXED_HTTP=true # DO NOT USE - deprecated
# HTTP Server Configuration (only used when MCP_MODE=http)
PORT=3000

File diff suppressed because it is too large Load Diff

View File

@@ -74,7 +74,8 @@ ENV AUTH_TOKEN="REPLACE_THIS_AUTH_TOKEN_32_CHARS_MIN_abcdefgh"
ENV NODE_ENV=production
ENV IS_DOCKER=true
ENV MCP_MODE=http
ENV USE_FIXED_HTTP=true
# NOTE: USE_FIXED_HTTP is deprecated. SingleSessionHTTPServer is now the default.
# See: https://github.com/czlonkowski/n8n-mcp/issues/524
ENV LOG_LEVEL=info
ENV TRUST_PROXY=1
ENV HOST=0.0.0.0

View File

@@ -5,7 +5,7 @@
[![npm version](https://img.shields.io/npm/v/n8n-mcp.svg)](https://www.npmjs.com/package/n8n-mcp)
[![codecov](https://codecov.io/gh/czlonkowski/n8n-mcp/graph/badge.svg?token=YOUR_TOKEN)](https://codecov.io/gh/czlonkowski/n8n-mcp)
[![Tests](https://img.shields.io/badge/tests-3336%20passing-brightgreen.svg)](https://github.com/czlonkowski/n8n-mcp/actions)
[![n8n version](https://img.shields.io/badge/n8n-2.1.4-orange.svg)](https://github.com/n8n-io/n8n)
[![n8n version](https://img.shields.io/badge/n8n-2.2.3-orange.svg)](https://github.com/n8n-io/n8n)
[![Docker](https://img.shields.io/badge/docker-ghcr.io%2Fczlonkowski%2Fn8n--mcp-green.svg)](https://github.com/czlonkowski/n8n-mcp/pkgs/container/n8n-mcp)
[![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/n8n-mcp?referralCode=n8n-mcp)

Binary file not shown.

View File

@@ -43,7 +43,7 @@ exports.getTemplateDoc = {
- url: Link to template on n8n.io
- workflow: Complete workflow JSON with structure:
- nodes: Array of node objects (id, name, type, typeVersion, position, parameters)
- connections: Object mapping source nodes to targets
- connections: Object mapping source node names to targets
- settings: Workflow configuration (timezone, error handling, etc.)
- usage: Instructions for using the workflow`,
examples: [

View File

@@ -21,7 +21,7 @@ exports.n8nCreateWorkflowDoc = {
parameters: {
name: { type: 'string', required: true, description: 'Workflow name' },
nodes: { type: 'array', required: true, description: 'Array of nodes with id, name, type, typeVersion, position, parameters' },
connections: { type: 'object', required: true, description: 'Node connections. Keys are source node IDs' },
connections: { type: 'object', required: true, description: 'Node connections. Keys are source node names (not IDs)' },
settings: { type: 'object', description: 'Optional workflow settings (timezone, error handling, etc.)' }
},
returns: 'Minimal summary (id, name, active, nodeCount) for token efficiency. Use n8n_get_workflow with mode "structure" to verify current state if needed.',
@@ -56,8 +56,8 @@ n8n_create_workflow({
}
],
connections: {
"webhook_1": {
"main": [[{node: "slack_1", type: "main", index: 0}]]
"Webhook": {
"main": [[{node: "Slack", type: "main", index: 0}]]
}
}
})`,

View File

@@ -1 +1 @@
{"version":3,"file":"n8n-create-workflow.js","sourceRoot":"","sources":["../../../../src/mcp/tool-docs/workflow_management/n8n-create-workflow.ts"],"names":[],"mappings":";;;AAEa,QAAA,oBAAoB,GAAsB;IACrD,IAAI,EAAE,qBAAqB;IAC3B,QAAQ,EAAE,qBAAqB;IAC/B,UAAU,EAAE;QACV,WAAW,EAAE,sGAAsG;QACnH,aAAa,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC;QAC/C,OAAO,EAAE,0EAA0E;QACnF,WAAW,EAAE,mBAAmB;QAChC,IAAI,EAAE;YACJ,2BAA2B;YAC3B,+BAA+B;YAC/B,uCAAuC;YACvC,kFAAkF;SACnF;KACF;IACD,IAAI,EAAE;QACJ,WAAW,EAAE,uLAAuL;QACpM,UAAU,EAAE;YACV,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,eAAe,EAAE;YACtE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,uEAAuE,EAAE;YAC9H,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,4CAA4C,EAAE;YAC1G,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,6DAA6D,EAAE;SACzG;QACD,OAAO,EAAE,mJAAmJ;QAC5J,QAAQ,EAAE;YACR;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCH;YACG;;;;;;;;;;;GAWH;SACE;QACD,QAAQ,EAAE;YACR,4BAA4B;YAC5B,4BAA4B;YAC5B,2BAA2B;YAC3B,qBAAqB;SACtB;QACD,WAAW,EAAE,oEAAoE;QACjF,aAAa,EAAE;YACb,uCAAuC;YACvC,qBAAqB;YACrB,gCAAgC;YAChC,6BAA6B;SAC9B;QACD,QAAQ,EAAE;YACR,0GAA0G;YAC1G,gEAAgE;YAChE,yCAAyC;YACzC,kDAAkD;YAClD,4EAA4E;YAC5E,yIAAyI;YACzI,uIAAuI;SACxI;QACD,YAAY,EAAE,CAAC,mBAAmB,EAAE,6BAA6B,EAAE,mBAAmB,CAAC;KACxF;CACF,CAAC"}
{"version":3,"file":"n8n-create-workflow.js","sourceRoot":"","sources":["../../../../src/mcp/tool-docs/workflow_management/n8n-create-workflow.ts"],"names":[],"mappings":";;;AAEa,QAAA,oBAAoB,GAAsB;IACrD,IAAI,EAAE,qBAAqB;IAC3B,QAAQ,EAAE,qBAAqB;IAC/B,UAAU,EAAE;QACV,WAAW,EAAE,sGAAsG;QACnH,aAAa,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC;QAC/C,OAAO,EAAE,0EAA0E;QACnF,WAAW,EAAE,mBAAmB;QAChC,IAAI,EAAE;YACJ,2BAA2B;YAC3B,+BAA+B;YAC/B,uCAAuC;YACvC,kFAAkF;SACnF;KACF;IACD,IAAI,EAAE;QACJ,WAAW,EAAE,uLAAuL;QACpM,UAAU,EAAE;YACV,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,eAAe,EAAE;YACtE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,uEAAuE,EAAE;YAC9H,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,wDAAwD,EAAE;YACtH,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,6DAA6D,EAAE;SACzG;QACD,OAAO,EAAE,mJAAmJ;QAC5J,QAAQ,EAAE;YACR;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCH;YACG;;;;;;;;;;;GAWH;SACE;QACD,QAAQ,EAAE;YACR,4BAA4B;YAC5B,4BAA4B;YAC5B,2BAA2B;YAC3B,qBAAqB;SACtB;QACD,WAAW,EAAE,oEAAoE;QACjF,aAAa,EAAE;YACb,uCAAuC;YACvC,qBAAqB;YACrB,gCAAgC;YAChC,6BAA6B;SAC9B;QACD,QAAQ,EAAE;YACR,0GAA0G;YAC1G,gEAAgE;YAChE,yCAAyC;YACzC,kDAAkD;YAClD,4EAA4E;YAC5E,yIAAyI;YACzI,uIAAuI;SACxI;QACD,YAAY,EAAE,CAAC,mBAAmB,EAAE,6BAA6B,EAAE,mBAAmB,CAAC;KACxF;CACF,CAAC"}

View File

@@ -1 +1 @@
{"version":3,"file":"tools-n8n-manager.d.ts","sourceRoot":"","sources":["../../src/mcp/tools-n8n-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAQ1C,eAAO,MAAM,kBAAkB,EAAE,cAAc,EAogB9C,CAAC"}
{"version":3,"file":"tools-n8n-manager.d.ts","sourceRoot":"","sources":["../../src/mcp/tools-n8n-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAQ1C,eAAO,MAAM,kBAAkB,EAAE,cAAc,EAqlB9C,CAAC"}

View File

@@ -42,7 +42,7 @@ exports.n8nManagementTools = [
},
connections: {
type: 'object',
description: 'Workflow connections object. Keys are source node IDs, values define output connections'
description: 'Workflow connections object. Keys are source node names (the name field, not id), values define output connections'
},
settings: {
type: 'object',
@@ -60,7 +60,13 @@ exports.n8nManagementTools = [
}
},
required: ['name', 'nodes', 'connections']
}
},
annotations: {
title: 'Create Workflow',
readOnlyHint: false,
destructiveHint: false,
openWorldHint: true,
},
},
{
name: 'n8n_get_workflow',
@@ -80,7 +86,13 @@ exports.n8nManagementTools = [
}
},
required: ['id']
}
},
annotations: {
title: 'Get Workflow',
readOnlyHint: true,
idempotentHint: true,
openWorldHint: true,
},
},
{
name: 'n8n_update_full_workflow',
@@ -114,7 +126,14 @@ exports.n8nManagementTools = [
}
},
required: ['id']
}
},
annotations: {
title: 'Update Full Workflow',
readOnlyHint: false,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true,
},
},
{
name: 'n8n_update_partial_workflow',
@@ -145,7 +164,14 @@ exports.n8nManagementTools = [
}
},
required: ['id', 'operations']
}
},
annotations: {
title: 'Update Partial Workflow',
readOnlyHint: false,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true,
},
},
{
name: 'n8n_delete_workflow',
@@ -159,7 +185,13 @@ exports.n8nManagementTools = [
}
},
required: ['id']
}
},
annotations: {
title: 'Delete Workflow',
readOnlyHint: false,
destructiveHint: true,
openWorldHint: true,
},
},
{
name: 'n8n_list_workflows',
@@ -193,7 +225,13 @@ exports.n8nManagementTools = [
description: 'Exclude pinned data from response (default: true)'
}
}
}
},
annotations: {
title: 'List Workflows',
readOnlyHint: true,
idempotentHint: true,
openWorldHint: true,
},
},
{
name: 'n8n_validate_workflow',
@@ -230,7 +268,13 @@ exports.n8nManagementTools = [
}
},
required: ['id']
}
},
annotations: {
title: 'Validate Workflow',
readOnlyHint: true,
idempotentHint: true,
openWorldHint: true,
},
},
{
name: 'n8n_autofix_workflow',
@@ -265,7 +309,14 @@ exports.n8nManagementTools = [
}
},
required: ['id']
}
},
annotations: {
title: 'Autofix Workflow',
readOnlyHint: false,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true,
},
},
{
name: 'n8n_test_workflow',
@@ -317,7 +368,13 @@ exports.n8nManagementTools = [
}
},
required: ['workflowId']
}
},
annotations: {
title: 'Test Workflow',
readOnlyHint: false,
destructiveHint: false,
openWorldHint: true,
},
},
{
name: 'n8n_executions',
@@ -395,7 +452,13 @@ exports.n8nManagementTools = [
}
},
required: ['action']
}
},
annotations: {
title: 'Manage Executions',
readOnlyHint: false,
destructiveHint: true,
openWorldHint: true,
},
},
{
name: 'n8n_health_check',
@@ -414,7 +477,13 @@ exports.n8nManagementTools = [
description: 'Include extra details in diagnostic mode (default: false)'
}
}
}
},
annotations: {
title: 'Health Check',
readOnlyHint: true,
idempotentHint: true,
openWorldHint: true,
},
},
{
name: 'n8n_workflow_versions',
@@ -468,7 +537,13 @@ exports.n8nManagementTools = [
}
},
required: ['mode']
}
},
annotations: {
title: 'Workflow Versions',
readOnlyHint: false,
destructiveHint: true,
openWorldHint: true,
},
},
{
name: 'n8n_deploy_template',
@@ -501,7 +576,13 @@ exports.n8nManagementTools = [
}
},
required: ['templateId']
}
},
annotations: {
title: 'Deploy Template',
readOnlyHint: false,
destructiveHint: false,
openWorldHint: true,
},
}
];
//# sourceMappingURL=tools-n8n-manager.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/mcp/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAQ1C,eAAO,MAAM,0BAA0B,EAAE,cAAc,EA+XtD,CAAC"}
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/mcp/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAQ1C,eAAO,MAAM,0BAA0B,EAAE,cAAc,EAkatD,CAAC"}

35
dist/mcp/tools.js vendored
View File

@@ -20,6 +20,11 @@ exports.n8nDocumentationToolsFinal = [
},
},
},
annotations: {
title: 'Tools Documentation',
readOnlyHint: true,
idempotentHint: true,
},
},
{
name: 'search_nodes',
@@ -50,6 +55,11 @@ exports.n8nDocumentationToolsFinal = [
},
required: ['query'],
},
annotations: {
title: 'Search Nodes',
readOnlyHint: true,
idempotentHint: true,
},
},
{
name: 'get_node',
@@ -103,6 +113,11 @@ exports.n8nDocumentationToolsFinal = [
},
required: ['nodeType'],
},
annotations: {
title: 'Get Node Info',
readOnlyHint: true,
idempotentHint: true,
},
},
{
name: 'validate_node',
@@ -183,6 +198,11 @@ exports.n8nDocumentationToolsFinal = [
},
required: ['nodeType', 'displayName', 'valid']
},
annotations: {
title: 'Validate Node Config',
readOnlyHint: true,
idempotentHint: true,
},
},
{
name: 'get_template',
@@ -203,6 +223,11 @@ exports.n8nDocumentationToolsFinal = [
},
required: ['templateId'],
},
annotations: {
title: 'Get Template',
readOnlyHint: true,
idempotentHint: true,
},
},
{
name: 'search_templates',
@@ -293,6 +318,11 @@ exports.n8nDocumentationToolsFinal = [
},
},
},
annotations: {
title: 'Search Templates',
readOnlyHint: true,
idempotentHint: true,
},
},
{
name: 'validate_workflow',
@@ -378,6 +408,11 @@ exports.n8nDocumentationToolsFinal = [
},
required: ['valid', 'summary']
},
annotations: {
title: 'Validate Workflow',
readOnlyHint: true,
idempotentHint: true,
},
},
];
//# sourceMappingURL=tools.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"node-similarity-service.d.ts","sourceRoot":"","sources":["../../src/services/node-similarity-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAG7D,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,qBAAqB;IAEhC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAM;IAC/C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAK;IAC/C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAK;IAChD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAiB;IAC1D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAO;IAElD,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,cAAc,CAAsC;IAC5D,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,YAAY,CAAa;gBAErB,UAAU,EAAE,cAAc;IAStC,OAAO,CAAC,wBAAwB;IAkDhC,OAAO,CAAC,yBAAyB;IAuB3B,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,GAAE,MAAU,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IA8CzF,OAAO,CAAC,mBAAmB;IA0E3B,OAAO,CAAC,wBAAwB;IAuEhC,OAAO,CAAC,gBAAgB;IA2BxB,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,mBAAmB;IAgB3B,OAAO,CAAC,eAAe;YAgDT,cAAc;IAqCrB,eAAe,IAAI,IAAI;IAUjB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ1C,uBAAuB,CAAC,WAAW,EAAE,cAAc,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM;IA8BnF,aAAa,CAAC,UAAU,EAAE,cAAc,GAAG,OAAO;IAQlD,UAAU,IAAI,IAAI;CAGnB"}
{"version":3,"file":"node-similarity-service.d.ts","sourceRoot":"","sources":["../../src/services/node-similarity-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAI7D,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,qBAAqB;IAEhC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAM;IAC/C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAK;IAC/C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAK;IAChD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAiB;IAC1D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,mBAAmB,CAAO;IAElD,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,cAAc,CAAsC;IAC5D,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,YAAY,CAAa;gBAErB,UAAU,EAAE,cAAc;IAStC,OAAO,CAAC,wBAAwB;IAkDhC,OAAO,CAAC,yBAAyB;IAuB3B,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,GAAE,MAAU,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAiEzF,OAAO,CAAC,mBAAmB;IA0E3B,OAAO,CAAC,wBAAwB;IAuEhC,OAAO,CAAC,gBAAgB;IA2BxB,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,mBAAmB;IAgB3B,OAAO,CAAC,eAAe;YAgDT,cAAc;IAqCrB,eAAe,IAAI,IAAI;IAUjB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ1C,uBAAuB,CAAC,WAAW,EAAE,cAAc,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM;IA8BnF,aAAa,CAAC,UAAU,EAAE,cAAc,GAAG,OAAO;IAQlD,UAAU,IAAI,IAAI;CAGnB"}

View File

@@ -2,6 +2,7 @@
Object.defineProperty(exports, "__esModule", { value: true });
exports.NodeSimilarityService = void 0;
const logger_1 = require("../utils/logger");
const tool_variant_generator_1 = require("./tool-variant-generator");
class NodeSimilarityService {
constructor(repository) {
this.nodeCache = null;
@@ -67,6 +68,22 @@ class NodeSimilarityService {
if (!invalidType || invalidType.trim() === '') {
return [];
}
if (tool_variant_generator_1.ToolVariantGenerator.isToolVariantNodeType(invalidType)) {
const baseNodeType = tool_variant_generator_1.ToolVariantGenerator.getBaseNodeType(invalidType);
if (baseNodeType) {
const baseNode = this.repository.getNode(baseNodeType);
if (baseNode) {
return [{
nodeType: invalidType,
displayName: `${baseNode.displayName} Tool`,
confidence: 0.98,
reason: `Dynamic AI Tool variant of ${baseNode.displayName}`,
category: baseNode.category,
description: 'Runtime-generated Tool variant for AI Agent integration'
}];
}
}
}
const suggestions = [];
const mistakeSuggestion = this.checkCommonMistakes(invalidType);
if (mistakeSuggestion) {

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"workflow-validator.d.ts","sourceRoot":"","sources":["../../src/services/workflow-validator.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"workflow-validator.d.ts","sourceRoot":"","sources":["../../src/services/workflow-validator.ts"],"names":[],"mappings":"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"}

View File

@@ -236,7 +236,31 @@ class WorkflowValidator {
}
}
const normalizedType = node_type_normalizer_1.NodeTypeNormalizer.normalizeToFullForm(node.type);
const nodeInfo = this.nodeRepository.getNode(normalizedType);
let nodeInfo = this.nodeRepository.getNode(normalizedType);
if (!nodeInfo && tool_variant_generator_1.ToolVariantGenerator.isToolVariantNodeType(normalizedType)) {
const baseNodeType = tool_variant_generator_1.ToolVariantGenerator.getBaseNodeType(normalizedType);
if (baseNodeType) {
const baseNodeInfo = this.nodeRepository.getNode(baseNodeType);
if (baseNodeInfo) {
result.warnings.push({
type: 'warning',
nodeId: node.id,
nodeName: node.name,
message: `Node type "${node.type}" is inferred as a dynamic AI Tool variant of "${baseNodeType}". ` +
`This Tool variant is created by n8n at runtime when connecting "${baseNodeInfo.displayName}" to an AI Agent.`,
code: 'INFERRED_TOOL_VARIANT'
});
nodeInfo = {
...baseNodeInfo,
nodeType: normalizedType,
displayName: `${baseNodeInfo.displayName} Tool`,
isToolVariant: true,
toolVariantOf: baseNodeType,
isInferred: true
};
}
}
}
if (!nodeInfo) {
const suggestions = await this.similarityService.findSimilarNodes(node.type, 3);
let message = `Unknown node type: "${node.type}".`;
@@ -310,6 +334,9 @@ class WorkflowValidator {
if (normalizedType.startsWith('nodes-langchain.')) {
continue;
}
if (nodeInfo.isInferred) {
continue;
}
const paramsWithVersion = {
'@version': node.typeVersion || 1,
...node.parameters

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"batch-processor.d.ts","sourceRoot":"","sources":["../../src/telemetry/batch-processor.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,sBAAsB,EAAoB,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAyBlI,qBAAa,uBAAuB;IAoBhC,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,SAAS;IApBnB,OAAO,CAAC,UAAU,CAAC,CAAiB;IACpC,OAAO,CAAC,gBAAgB,CAAkB;IAC1C,OAAO,CAAC,mBAAmB,CAAkB;IAC7C,OAAO,CAAC,mBAAmB,CAAkB;IAC7C,OAAO,CAAC,cAAc,CAA0B;IAChD,OAAO,CAAC,OAAO,CAQb;IACF,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,eAAe,CAAuE;IAC9F,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAO;gBAG/B,QAAQ,EAAE,cAAc,GAAG,IAAI,EAC/B,SAAS,EAAE,MAAM,OAAO;IAQlC,KAAK,IAAI,IAAI;IA+Bb,IAAI,IAAI,IAAI;IAWN,KAAK,CAAC,MAAM,CAAC,EAAE,cAAc,EAAE,EAAE,SAAS,CAAC,EAAE,iBAAiB,EAAE,EAAE,SAAS,CAAC,EAAE,sBAAsB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;YAgD9G,WAAW;YAmDX,cAAc;YAuDd,cAAc;YAiEd,gBAAgB;IAgD9B,OAAO,CAAC,aAAa;IAarB,OAAO,CAAC,oBAAoB;IAiB5B,OAAO,CAAC,oBAAoB;YAmBd,sBAAsB;IAgCpC,OAAO,CAAC,eAAe;IAiBvB,UAAU,IAAI,gBAAgB,GAAG;QAAE,mBAAmB,EAAE,GAAG,CAAC;QAAC,mBAAmB,EAAE,MAAM,CAAA;KAAE;IAW1F,YAAY,IAAI,IAAI;CAarB"}
{"version":3,"file":"batch-processor.d.ts","sourceRoot":"","sources":["../../src/telemetry/batch-processor.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,sBAAsB,EAAoB,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAoClI,qBAAa,uBAAuB;IAoBhC,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,SAAS;IApBnB,OAAO,CAAC,UAAU,CAAC,CAAiB;IACpC,OAAO,CAAC,gBAAgB,CAAkB;IAC1C,OAAO,CAAC,mBAAmB,CAAkB;IAC7C,OAAO,CAAC,mBAAmB,CAAkB;IAC7C,OAAO,CAAC,cAAc,CAA0B;IAChD,OAAO,CAAC,OAAO,CAQb;IACF,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,eAAe,CAAuE;IAC9F,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAO;gBAG/B,QAAQ,EAAE,cAAc,GAAG,IAAI,EAC/B,SAAS,EAAE,MAAM,OAAO;IAQlC,KAAK,IAAI,IAAI;IA+Bb,IAAI,IAAI,IAAI;IAWN,KAAK,CAAC,MAAM,CAAC,EAAE,cAAc,EAAE,EAAE,SAAS,CAAC,EAAE,iBAAiB,EAAE,EAAE,SAAS,CAAC,EAAE,sBAAsB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;YAgD9G,WAAW;YAmDX,cAAc;YAuDd,cAAc;YAiEd,gBAAgB;IAgD9B,OAAO,CAAC,aAAa;IAarB,OAAO,CAAC,oBAAoB;IAiB5B,OAAO,CAAC,oBAAoB;YAmBd,sBAAsB;IAgCpC,OAAO,CAAC,eAAe;IAiBvB,UAAU,IAAI,gBAAgB,GAAG;QAAE,mBAAmB,EAAE,GAAG,CAAC;QAAC,mBAAmB,EAAE,MAAM,CAAA;KAAE;IAW1F,YAAY,IAAI,IAAI;CAarB"}

View File

@@ -4,19 +4,13 @@ exports.TelemetryBatchProcessor = void 0;
const telemetry_types_1 = require("./telemetry-types");
const telemetry_error_1 = require("./telemetry-error");
const logger_1 = require("../utils/logger");
function toSnakeCase(obj) {
if (obj === null || obj === undefined)
return obj;
if (Array.isArray(obj))
return obj.map(toSnakeCase);
if (typeof obj !== 'object')
return obj;
function keyToSnakeCase(key) {
return key.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
}
function mutationToSupabaseFormat(mutation) {
const result = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const snakeKey = key.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
result[snakeKey] = toSnakeCase(obj[key]);
}
for (const [key, value] of Object.entries(mutation)) {
result[keyToSnakeCase(key)] = value;
}
return result;
}
@@ -185,7 +179,7 @@ class TelemetryBatchProcessor {
const batches = this.createBatches(mutations, telemetry_types_1.TELEMETRY_CONFIG.MAX_BATCH_SIZE);
for (const batch of batches) {
const result = await this.executeWithRetry(async () => {
const snakeCaseBatch = batch.map(mutation => toSnakeCase(mutation));
const snakeCaseBatch = batch.map(mutation => mutationToSupabaseFormat(mutation));
const { error } = await this.supabase
.from('workflow_mutations')
.insert(snakeCaseBatch);

File diff suppressed because one or more lines are too long

View File

@@ -7,6 +7,13 @@ export interface MCPServerConfig {
host: string;
authToken?: string;
}
export interface ToolAnnotations {
title?: string;
readOnlyHint?: boolean;
destructiveHint?: boolean;
idempotentHint?: boolean;
openWorldHint?: boolean;
}
export interface ToolDefinition {
name: string;
description: string;
@@ -22,6 +29,7 @@ export interface ToolDefinition {
required?: string[];
additionalProperties?: boolean | Record<string, any>;
};
annotations?: ToolAnnotations;
}
export interface ResourceDefinition {
uri: string;

View File

@@ -1 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AACA,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAEhC,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE;QACX,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAChC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,oBAAoB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;KACtD,CAAC;IACF,YAAY,CAAC,EAAE;QACb,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAChC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,oBAAoB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;KACtD,CAAC;CACH;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,KAAK,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;KACpB,CAAC,CAAC;CACJ"}
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AACA,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAEhC,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAMD,MAAM,WAAW,eAAe;IAE9B,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE;QACX,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAChC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,oBAAoB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;KACtD,CAAC;IACF,YAAY,CAAC,EAAE;QACb,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAChC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,oBAAoB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;KACtD,CAAC;IAEF,WAAW,CAAC,EAAE,eAAe,CAAC;CAC/B;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,KAAK,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;KACpB,CAAC,CAAC;CACJ"}

View File

@@ -12,7 +12,8 @@ services:
environment:
# Mode configuration
MCP_MODE: ${MCP_MODE:-http}
USE_FIXED_HTTP: ${USE_FIXED_HTTP:-true} # Use fixed implementation for stability
# NOTE: USE_FIXED_HTTP is deprecated. SingleSessionHTTPServer is now the default.
# See: https://github.com/czlonkowski/n8n-mcp/issues/524
AUTH_TOKEN: ${AUTH_TOKEN:?AUTH_TOKEN is required for HTTP mode}
# Application settings

View File

@@ -21,7 +21,6 @@ cd n8n-mcp
# Create .env file with auth token
cat > .env << EOF
AUTH_TOKEN=$(openssl rand -base64 32)
USE_FIXED_HTTP=true
EOF
# Start the server
@@ -46,7 +45,6 @@ docker pull ghcr.io/czlonkowski/n8n-mcp:latest
docker run -d \
--name n8n-mcp \
-e MCP_MODE=http \
-e USE_FIXED_HTTP=true \
-e AUTH_TOKEN=your-secure-token \
-p 3000:3000 \
ghcr.io/czlonkowski/n8n-mcp:latest

View File

@@ -67,7 +67,6 @@ Claude Desktop → mcp-remote → https://your-server.com
# 1. Create environment file
cat > .env << EOF
AUTH_TOKEN=$(openssl rand -base64 32)
USE_FIXED_HTTP=true
MCP_MODE=http
PORT=3000
# Optional: Enable n8n management tools
@@ -106,7 +105,6 @@ npm run rebuild
# 2. Configure environment
export MCP_MODE=http
export USE_FIXED_HTTP=true # Important: Use fixed implementation
export AUTH_TOKEN=$(openssl rand -base64 32)
export PORT=3000
@@ -144,7 +142,6 @@ Skip HTTP entirely and use stdio mode directly:
| Variable | Description | Example |
|----------|-------------|------|
| `MCP_MODE` | Must be set to `http` | `http` |
| `USE_FIXED_HTTP` | **Important**: Set to `true` for stable implementation | `true` |
| `AUTH_TOKEN` or `AUTH_TOKEN_FILE` | Authentication method | See security section |
### Optional Settings
@@ -417,7 +414,6 @@ services:
environment:
# Core configuration
MCP_MODE: http
USE_FIXED_HTTP: true
NODE_ENV: production
# Security - Using file-based secret
@@ -500,7 +496,6 @@ WorkingDirectory=/opt/n8n-mcp
# Use file-based secret
Environment="AUTH_TOKEN_FILE=/etc/n8n-mcp/auth_token"
Environment="MCP_MODE=http"
Environment="USE_FIXED_HTTP=true"
Environment="NODE_ENV=production"
Environment="TRUST_PROXY=1"
Environment="BASE_URL=https://n8n-mcp.example.com"
@@ -772,8 +767,8 @@ sudo ufw status # Linux
```
**"Stream is not readable":**
- Ensure `USE_FIXED_HTTP=true` is set
- Fixed in v2.3.2+
- This issue was fixed in v2.3.2+ with the SingleSessionHTTPServer
- No additional configuration needed
**Bridge script not working:**
```bash

View File

@@ -18,7 +18,6 @@ The fastest way to get n8n-MCP running:
# Using Docker (recommended)
cat > .env << EOF
AUTH_TOKEN=$(openssl rand -base64 32)
USE_FIXED_HTTP=true
EOF
docker compose up -d
```
@@ -49,7 +48,6 @@ docker compose up -d
environment:
MCP_MODE: ${MCP_MODE:-http}
USE_FIXED_HTTP: ${USE_FIXED_HTTP:-true}
AUTH_TOKEN: ${AUTH_TOKEN:?AUTH_TOKEN is required}
NODE_ENV: ${NODE_ENV:-production}
LOG_LEVEL: ${LOG_LEVEL:-info}

View File

@@ -98,7 +98,6 @@ These are automatically set by the Railway template:
|----------|--------------|-------------|
| `AUTH_TOKEN` | `REPLACE_THIS...` | **⚠️ CHANGE IMMEDIATELY** |
| `MCP_MODE` | `http` | Required for cloud deployment |
| `USE_FIXED_HTTP` | `true` | Stable HTTP implementation |
| `NODE_ENV` | `production` | Production optimizations |
| `LOG_LEVEL` | `info` | Balanced logging |
| `TRUST_PROXY` | `1` | Railway runs behind proxy |

View File

@@ -40,7 +40,6 @@ Key configuration options:
| Variable | Description | Default |
|----------|-------------|---------|
| `MCP_MODE` | Server mode: `stdio` or `http` | `stdio` |
| `USE_FIXED_HTTP` | Use fixed HTTP implementation (v2.3.2+) | `true` |
| `AUTH_TOKEN` | Authentication token for HTTP mode | Required |
| `PORT` | HTTP server port | `3000` |
| `LOG_LEVEL` | Logging verbosity | `info` |

601
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "n8n-mcp",
"version": "2.31.2",
"version": "2.31.9",
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@@ -22,9 +22,9 @@
"test-nodes": "node dist/scripts/test-nodes.js",
"start": "node dist/mcp/index.js",
"start:http": "MCP_MODE=http node dist/mcp/index.js",
"start:http:fixed": "MCP_MODE=http USE_FIXED_HTTP=true node dist/mcp/index.js",
"start:http:fixed:deprecated": "echo 'DEPRECATED: USE_FIXED_HTTP is deprecated. Use npm run start:http instead.' && MCP_MODE=http USE_FIXED_HTTP=true node dist/mcp/index.js",
"start:n8n": "N8N_MODE=true MCP_MODE=http node dist/mcp/index.js",
"http": "npm run build && npm run start:http:fixed",
"http": "npm run build && npm run start:http",
"dev": "npm run build && npm run rebuild && npm run validate",
"dev:http": "MCP_MODE=http nodemon --watch src --ext ts --exec 'npm run build && npm run start:http'",
"test:single-session": "./scripts/test-single-session.sh",
@@ -141,16 +141,16 @@
},
"dependencies": {
"@modelcontextprotocol/sdk": "1.20.1",
"@n8n/n8n-nodes-langchain": "^2.1.3",
"@n8n/n8n-nodes-langchain": "^2.2.2",
"@supabase/supabase-js": "^2.57.4",
"dotenv": "^16.5.0",
"express": "^5.1.0",
"express-rate-limit": "^7.1.5",
"form-data": "^4.0.5",
"lru-cache": "^11.2.1",
"n8n": "^2.1.4",
"n8n-core": "^2.1.3",
"n8n-workflow": "^2.1.1",
"n8n": "^2.2.3",
"n8n-core": "^2.2.2",
"n8n-workflow": "^2.2.2",
"openai": "^4.77.0",
"sql.js": "^1.13.0",
"tslib": "^2.6.2",

View File

@@ -71,10 +71,12 @@ const testCases: TestCase[] = [
}
},
{
name: 'Fixed HTTP implementation',
// DEPRECATED: This test case tests the deprecated fixed HTTP implementation
// See: https://github.com/czlonkowski/n8n-mcp/issues/524
name: 'Fixed HTTP implementation (DEPRECATED)',
env: {
MCP_MODE: 'http',
USE_FIXED_HTTP: 'true',
USE_FIXED_HTTP: 'true', // DEPRECATED: Will be removed in future version
AUTH_TOKEN: 'test-token-for-testing-only',
PORT: '3005',
BASE_URL: 'https://fixed.example.com'

View File

@@ -1,7 +1,14 @@
#!/usr/bin/env node
/**
* Fixed HTTP server for n8n-MCP that properly handles StreamableHTTPServerTransport initialization
* This implementation ensures the transport is properly initialized before handling requests
* @deprecated This fixed HTTP server is deprecated as of v2.31.8.
* Use SingleSessionHTTPServer from http-server-single-session.ts instead.
*
* This implementation does not support SSE streaming required by clients like OpenAI Codex.
* See: https://github.com/czlonkowski/n8n-mcp/issues/524
*
* Original purpose: Fixed HTTP server for n8n-MCP that properly handles
* StreamableHTTPServerTransport initialization by bypassing it entirely.
* This implementation ensures the transport is properly initialized before handling requests.
*/
import express from 'express';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
@@ -125,7 +132,18 @@ async function shutdown() {
}
}
/**
* @deprecated Use SingleSessionHTTPServer from http-server-single-session.ts instead.
* This function does not support SSE streaming required by clients like OpenAI Codex.
*/
export async function startFixedHTTPServer() {
// Log deprecation warning
logger.warn(
'DEPRECATION: startFixedHTTPServer() is deprecated as of v2.31.8. ' +
'Use SingleSessionHTTPServer which supports SSE streaming. ' +
'See: https://github.com/czlonkowski/n8n-mcp/issues/524'
);
validateEnvironment();
const app = express();

View File

@@ -124,9 +124,23 @@ Learn more: https://github.com/czlonkowski/n8n-mcp/blob/main/PRIVACY.md
checkpoints.push(STARTUP_CHECKPOINTS.MCP_HANDSHAKE_STARTING);
if (mode === 'http') {
// Check if we should use the fixed implementation
// Check if we should use the fixed implementation (DEPRECATED)
if (process.env.USE_FIXED_HTTP === 'true') {
// Use the fixed HTTP implementation that bypasses StreamableHTTPServerTransport issues
// DEPRECATION WARNING: Fixed HTTP implementation is deprecated
// It does not support SSE streaming required by clients like OpenAI Codex
logger.warn(
'DEPRECATION WARNING: USE_FIXED_HTTP=true is deprecated as of v2.31.8. ' +
'The fixed HTTP implementation does not support SSE streaming required by clients like OpenAI Codex. ' +
'Please unset USE_FIXED_HTTP to use the modern SingleSessionHTTPServer which supports both JSON-RPC and SSE. ' +
'This option will be removed in a future version. See: https://github.com/czlonkowski/n8n-mcp/issues/524'
);
console.warn('\n⚠ DEPRECATION WARNING ⚠️');
console.warn('USE_FIXED_HTTP=true is deprecated as of v2.31.8.');
console.warn('The fixed HTTP implementation does not support SSE streaming.');
console.warn('Please unset USE_FIXED_HTTP to use SingleSessionHTTPServer.');
console.warn('See: https://github.com/czlonkowski/n8n-mcp/issues/524\n');
// Use the deprecated fixed HTTP implementation
const { startFixedHTTPServer } = await import('../http-server');
await startFixedHTTPServer();
} else {

View File

@@ -42,7 +42,7 @@ export const getTemplateDoc: ToolDocumentation = {
- url: Link to template on n8n.io
- workflow: Complete workflow JSON with structure:
- nodes: Array of node objects (id, name, type, typeVersion, position, parameters)
- connections: Object mapping source nodes to targets
- connections: Object mapping source node names to targets
- settings: Workflow configuration (timezone, error handling, etc.)
- usage: Instructions for using the workflow`,
examples: [

View File

@@ -20,7 +20,7 @@ export const n8nCreateWorkflowDoc: ToolDocumentation = {
parameters: {
name: { type: 'string', required: true, description: 'Workflow name' },
nodes: { type: 'array', required: true, description: 'Array of nodes with id, name, type, typeVersion, position, parameters' },
connections: { type: 'object', required: true, description: 'Node connections. Keys are source node IDs' },
connections: { type: 'object', required: true, description: 'Node connections. Keys are source node names (not IDs)' },
settings: { type: 'object', description: 'Optional workflow settings (timezone, error handling, etc.)' }
},
returns: 'Minimal summary (id, name, active, nodeCount) for token efficiency. Use n8n_get_workflow with mode "structure" to verify current state if needed.',
@@ -55,8 +55,8 @@ n8n_create_workflow({
}
],
connections: {
"webhook_1": {
"main": [[{node: "slack_1", type: "main", index: 0}]]
"Webhook": {
"main": [[{node: "Slack", type: "main", index: 0}]]
}
}
})`,

View File

@@ -46,9 +46,9 @@ export const n8nManagementTools: ToolDefinition[] = [
}
}
},
connections: {
type: 'object',
description: 'Workflow connections object. Keys are source node IDs, values define output connections'
connections: {
type: 'object',
description: 'Workflow connections object. Keys are source node names (the name field, not id), values define output connections'
},
settings: {
type: 'object',
@@ -66,7 +66,13 @@ export const n8nManagementTools: ToolDefinition[] = [
}
},
required: ['name', 'nodes', 'connections']
}
},
annotations: {
title: 'Create Workflow',
readOnlyHint: false,
destructiveHint: false,
openWorldHint: true,
},
},
{
name: 'n8n_get_workflow',
@@ -86,7 +92,13 @@ export const n8nManagementTools: ToolDefinition[] = [
}
},
required: ['id']
}
},
annotations: {
title: 'Get Workflow',
readOnlyHint: true,
idempotentHint: true,
openWorldHint: true,
},
},
{
name: 'n8n_update_full_workflow',
@@ -120,7 +132,14 @@ export const n8nManagementTools: ToolDefinition[] = [
}
},
required: ['id']
}
},
annotations: {
title: 'Update Full Workflow',
readOnlyHint: false,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true,
},
},
{
name: 'n8n_update_partial_workflow',
@@ -151,7 +170,14 @@ export const n8nManagementTools: ToolDefinition[] = [
}
},
required: ['id', 'operations']
}
},
annotations: {
title: 'Update Partial Workflow',
readOnlyHint: false,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true,
},
},
{
name: 'n8n_delete_workflow',
@@ -165,7 +191,13 @@ export const n8nManagementTools: ToolDefinition[] = [
}
},
required: ['id']
}
},
annotations: {
title: 'Delete Workflow',
readOnlyHint: false,
destructiveHint: true,
openWorldHint: true,
},
},
{
name: 'n8n_list_workflows',
@@ -194,12 +226,18 @@ export const n8nManagementTools: ToolDefinition[] = [
type: 'string',
description: 'Filter by project ID (enterprise feature)'
},
excludePinnedData: {
type: 'boolean',
description: 'Exclude pinned data from response (default: true)'
excludePinnedData: {
type: 'boolean',
description: 'Exclude pinned data from response (default: true)'
}
}
}
},
annotations: {
title: 'List Workflows',
readOnlyHint: true,
idempotentHint: true,
openWorldHint: true,
},
},
{
name: 'n8n_validate_workflow',
@@ -227,16 +265,22 @@ export const n8nManagementTools: ToolDefinition[] = [
type: 'boolean',
description: 'Validate n8n expressions (default: true)'
},
profile: {
type: 'string',
profile: {
type: 'string',
enum: ['minimal', 'runtime', 'ai-friendly', 'strict'],
description: 'Validation profile to use (default: runtime)'
description: 'Validation profile to use (default: runtime)'
}
}
}
},
required: ['id']
}
},
annotations: {
title: 'Validate Workflow',
readOnlyHint: true,
idempotentHint: true,
openWorldHint: true,
},
},
{
name: 'n8n_autofix_workflow',
@@ -271,7 +315,14 @@ export const n8nManagementTools: ToolDefinition[] = [
}
},
required: ['id']
}
},
annotations: {
title: 'Autofix Workflow',
readOnlyHint: false,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true,
},
},
// Execution Management Tools
@@ -328,7 +379,13 @@ export const n8nManagementTools: ToolDefinition[] = [
}
},
required: ['workflowId']
}
},
annotations: {
title: 'Test Workflow',
readOnlyHint: false,
destructiveHint: false,
openWorldHint: true,
},
},
{
name: 'n8n_executions',
@@ -410,7 +467,13 @@ export const n8nManagementTools: ToolDefinition[] = [
}
},
required: ['action']
}
},
annotations: {
title: 'Manage Executions',
readOnlyHint: false,
destructiveHint: true,
openWorldHint: true,
},
},
// System Tools
@@ -431,7 +494,13 @@ export const n8nManagementTools: ToolDefinition[] = [
description: 'Include extra details in diagnostic mode (default: false)'
}
}
}
},
annotations: {
title: 'Health Check',
readOnlyHint: true,
idempotentHint: true,
openWorldHint: true,
},
},
{
name: 'n8n_workflow_versions',
@@ -485,7 +554,13 @@ export const n8nManagementTools: ToolDefinition[] = [
}
},
required: ['mode']
}
},
annotations: {
title: 'Workflow Versions',
readOnlyHint: false,
destructiveHint: true,
openWorldHint: true,
},
},
// Template Deployment Tool
@@ -520,6 +595,12 @@ export const n8nManagementTools: ToolDefinition[] = [
}
},
required: ['templateId']
}
},
annotations: {
title: 'Deploy Template',
readOnlyHint: false,
destructiveHint: false,
openWorldHint: true,
},
}
];
];

View File

@@ -25,6 +25,11 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [
},
},
},
annotations: {
title: 'Tools Documentation',
readOnlyHint: true,
idempotentHint: true,
},
},
{
name: 'search_nodes',
@@ -55,6 +60,11 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [
},
required: ['query'],
},
annotations: {
title: 'Search Nodes',
readOnlyHint: true,
idempotentHint: true,
},
},
{
name: 'get_node',
@@ -108,6 +118,11 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [
},
required: ['nodeType'],
},
annotations: {
title: 'Get Node Info',
readOnlyHint: true,
idempotentHint: true,
},
},
{
name: 'validate_node',
@@ -188,6 +203,11 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [
},
required: ['nodeType', 'displayName', 'valid']
},
annotations: {
title: 'Validate Node Config',
readOnlyHint: true,
idempotentHint: true,
},
},
{
name: 'get_template',
@@ -208,6 +228,11 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [
},
required: ['templateId'],
},
annotations: {
title: 'Get Template',
readOnlyHint: true,
idempotentHint: true,
},
},
{
name: 'search_templates',
@@ -303,6 +328,11 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [
},
},
},
annotations: {
title: 'Search Templates',
readOnlyHint: true,
idempotentHint: true,
},
},
{
name: 'validate_workflow',
@@ -388,6 +418,11 @@ export const n8nDocumentationToolsFinal: ToolDefinition[] = [
},
required: ['valid', 'summary']
},
annotations: {
title: 'Validate Workflow',
readOnlyHint: true,
idempotentHint: true,
},
},
];

View File

@@ -1,5 +1,6 @@
import { NodeRepository } from '../database/node-repository';
import { logger } from '../utils/logger';
import { ToolVariantGenerator } from './tool-variant-generator';
export interface NodeSuggestion {
nodeType: string;
@@ -126,6 +127,25 @@ export class NodeSimilarityService {
return [];
}
// Check if this is a Tool variant and base node exists (Issue #522)
// Dynamic AI Tool variants like googleDriveTool are created at runtime by n8n
if (ToolVariantGenerator.isToolVariantNodeType(invalidType)) {
const baseNodeType = ToolVariantGenerator.getBaseNodeType(invalidType);
if (baseNodeType) {
const baseNode = this.repository.getNode(baseNodeType);
if (baseNode) {
return [{
nodeType: invalidType,
displayName: `${baseNode.displayName} Tool`,
confidence: 0.98,
reason: `Dynamic AI Tool variant of ${baseNode.displayName}`,
category: baseNode.category,
description: 'Runtime-generated Tool variant for AI Agent integration'
}];
}
}
}
const suggestions: NodeSuggestion[] = [];
// First, check for exact common mistakes

View File

@@ -398,7 +398,39 @@ export class WorkflowValidator {
const normalizedType = NodeTypeNormalizer.normalizeToFullForm(node.type);
// Get node definition using normalized type (needed for typeVersion validation)
const nodeInfo = this.nodeRepository.getNode(normalizedType);
let nodeInfo = this.nodeRepository.getNode(normalizedType);
// Check if this is a dynamic Tool variant (e.g., googleDriveTool, googleSheetsTool)
// n8n creates these at runtime when ANY node is used in an AI Agent's tool slot,
// but they don't exist in npm packages. We infer validity if the base node exists.
// See: https://github.com/czlonkowski/n8n-mcp/issues/522
if (!nodeInfo && ToolVariantGenerator.isToolVariantNodeType(normalizedType)) {
const baseNodeType = ToolVariantGenerator.getBaseNodeType(normalizedType);
if (baseNodeType) {
const baseNodeInfo = this.nodeRepository.getNode(baseNodeType);
if (baseNodeInfo) {
// Valid inferred tool variant - base node exists
result.warnings.push({
type: 'warning',
nodeId: node.id,
nodeName: node.name,
message: `Node type "${node.type}" is inferred as a dynamic AI Tool variant of "${baseNodeType}". ` +
`This Tool variant is created by n8n at runtime when connecting "${baseNodeInfo.displayName}" to an AI Agent.`,
code: 'INFERRED_TOOL_VARIANT'
});
// Create synthetic nodeInfo for validation continuity
nodeInfo = {
...baseNodeInfo,
nodeType: normalizedType,
displayName: `${baseNodeInfo.displayName} Tool`,
isToolVariant: true,
toolVariantOf: baseNodeType,
isInferred: true
};
}
}
}
if (!nodeInfo) {
@@ -494,6 +526,13 @@ export class WorkflowValidator {
continue;
}
// Skip PARAMETER validation for inferred tool variants (Issue #522)
// They have a different property structure (toolDescription added at runtime)
// that doesn't match the base node's schema. TypeVersion validation above still runs.
if ((nodeInfo as any).isInferred) {
continue;
}
// Validate node configuration
// Add @version to parameters for displayOptions evaluation (supports _cnd operators)
const paramsWithVersion = {

View File

@@ -9,23 +9,34 @@ import { TelemetryError, TelemetryErrorType, TelemetryCircuitBreaker } from './t
import { logger } from '../utils/logger';
/**
* Convert camelCase object keys to snake_case
* Needed because Supabase PostgREST doesn't auto-convert
* Convert camelCase key to snake_case
*/
function toSnakeCase(obj: any): any {
if (obj === null || obj === undefined) return obj;
if (Array.isArray(obj)) return obj.map(toSnakeCase);
if (typeof obj !== 'object') return obj;
function keyToSnakeCase(key: string): string {
return key.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
}
const result: any = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
// Convert camelCase to snake_case
const snakeKey = key.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
// Recursively convert nested objects
result[snakeKey] = toSnakeCase(obj[key]);
}
/**
* Convert WorkflowMutationRecord to Supabase-compatible format.
*
* IMPORTANT: Only converts top-level field names to snake_case.
* Nested workflow data (workflowBefore, workflowAfter, operations, etc.)
* is preserved EXACTLY as-is to maintain n8n API compatibility.
*
* The Supabase workflow_mutations table stores workflow_before and
* workflow_after as JSONB columns, which preserve the original structure.
* Only the top-level columns (user_id, session_id, etc.) require snake_case.
*
* Issue #517: Previously this used recursive conversion which mangled:
* - Connection keys (node names like "Webhook" → "_webhook")
* - Node field names (typeVersion → type_version)
*/
function mutationToSupabaseFormat(mutation: WorkflowMutationRecord): Record<string, any> {
const result: Record<string, any> = {};
for (const [key, value] of Object.entries(mutation)) {
result[keyToSnakeCase(key)] = value;
}
return result;
}
@@ -266,7 +277,7 @@ export class TelemetryBatchProcessor {
for (const batch of batches) {
const result = await this.executeWithRetry(async () => {
// Convert camelCase to snake_case for Supabase
const snakeCaseBatch = batch.map(mutation => toSnakeCase(mutation));
const snakeCaseBatch = batch.map(mutation => mutationToSupabaseFormat(mutation));
const { error } = await this.supabase!
.from('workflow_mutations')

View File

@@ -10,6 +10,23 @@ export interface MCPServerConfig {
authToken?: string;
}
/**
* MCP Tool annotations to help AI assistants understand tool behavior.
* Per MCP spec: https://spec.modelcontextprotocol.io/specification/2025-03-26/server/tools/#annotations
*/
export interface ToolAnnotations {
/** Human-readable title for the tool */
title?: string;
/** If true, the tool does not modify its environment */
readOnlyHint?: boolean;
/** If true, the tool may perform destructive updates to its environment */
destructiveHint?: boolean;
/** If true, calling the tool repeatedly with the same arguments has no additional effect */
idempotentHint?: boolean;
/** If true, the tool may interact with external entities (APIs, services) */
openWorldHint?: boolean;
}
export interface ToolDefinition {
name: string;
description: string;
@@ -25,6 +42,8 @@ export interface ToolDefinition {
required?: string[];
additionalProperties?: boolean | Record<string, any>;
};
/** Tool behavior hints for AI assistants */
annotations?: ToolAnnotations;
}
export interface ResourceDefinition {

View File

@@ -599,4 +599,294 @@ describe('WorkflowValidator - Tool Variant Validation', () => {
expect(invalidToolErrors.length).toBeGreaterThan(0);
});
});
describe('validateAllNodes - Inferred Tool Variants (Issue #522)', () => {
/**
* Tests for dynamic AI Tool nodes that are created at runtime by n8n
* when ANY node is used in an AI Agent's tool slot.
*
* These nodes (e.g., googleDriveTool, googleSheetsTool) don't exist in npm packages
* but are valid when the base node exists.
*/
beforeEach(() => {
// Update mock repository to include Google nodes
mockRepository.getNode = vi.fn((nodeType: string) => {
// Base node with Tool variant
if (nodeType === 'nodes-base.supabase') {
return {
nodeType: 'nodes-base.supabase',
displayName: 'Supabase',
isAITool: true,
hasToolVariant: true,
isToolVariant: false,
isTrigger: false,
properties: []
};
}
// Tool variant in database
if (nodeType === 'nodes-base.supabaseTool') {
return {
nodeType: 'nodes-base.supabaseTool',
displayName: 'Supabase Tool',
isAITool: true,
hasToolVariant: false,
isToolVariant: true,
toolVariantOf: 'nodes-base.supabase',
isTrigger: false,
properties: []
};
}
// Google Drive base node (exists, but no Tool variant in DB)
if (nodeType === 'nodes-base.googleDrive') {
return {
nodeType: 'nodes-base.googleDrive',
displayName: 'Google Drive',
isAITool: false, // Not marked as AI tool in npm package
hasToolVariant: false, // No Tool variant in database
isToolVariant: false,
isTrigger: false,
properties: [],
category: 'files'
};
}
// Google Sheets base node (exists, but no Tool variant in DB)
if (nodeType === 'nodes-base.googleSheets') {
return {
nodeType: 'nodes-base.googleSheets',
displayName: 'Google Sheets',
isAITool: false,
hasToolVariant: false,
isToolVariant: false,
isTrigger: false,
properties: [],
category: 'productivity'
};
}
// AI Agent node
if (nodeType === 'nodes-langchain.agent') {
return {
nodeType: 'nodes-langchain.agent',
displayName: 'AI Agent',
isAITool: false,
hasToolVariant: false,
isToolVariant: false,
isTrigger: false,
properties: []
};
}
return null; // Unknown node
}) as any;
});
it('should pass validation for googleDriveTool when googleDrive exists', async () => {
const workflow = {
nodes: [
{
id: 'drive-tool-1',
name: 'Google Drive Tool',
type: 'n8n-nodes-base.googleDriveTool',
typeVersion: 3,
position: [250, 300] as [number, number],
parameters: {}
}
],
connections: {}
};
const result = await validator.validateWorkflow(workflow);
// Should NOT have "Unknown node type" error
const unknownErrors = result.errors.filter(e =>
e.message && e.message.includes('Unknown node type')
);
expect(unknownErrors).toHaveLength(0);
// Should have INFERRED_TOOL_VARIANT warning
const inferredWarnings = result.warnings.filter(e =>
(e as any).code === 'INFERRED_TOOL_VARIANT'
);
expect(inferredWarnings).toHaveLength(1);
expect(inferredWarnings[0].message).toContain('googleDriveTool');
expect(inferredWarnings[0].message).toContain('Google Drive');
});
it('should pass validation for googleSheetsTool when googleSheets exists', async () => {
const workflow = {
nodes: [
{
id: 'sheets-tool-1',
name: 'Google Sheets Tool',
type: 'n8n-nodes-base.googleSheetsTool',
typeVersion: 4,
position: [250, 300] as [number, number],
parameters: {}
}
],
connections: {}
};
const result = await validator.validateWorkflow(workflow);
// Should NOT have "Unknown node type" error
const unknownErrors = result.errors.filter(e =>
e.message && e.message.includes('Unknown node type')
);
expect(unknownErrors).toHaveLength(0);
// Should have INFERRED_TOOL_VARIANT warning
const inferredWarnings = result.warnings.filter(e =>
(e as any).code === 'INFERRED_TOOL_VARIANT'
);
expect(inferredWarnings).toHaveLength(1);
expect(inferredWarnings[0].message).toContain('googleSheetsTool');
expect(inferredWarnings[0].message).toContain('Google Sheets');
});
it('should report error for unknownNodeTool when base node does not exist', async () => {
const workflow = {
nodes: [
{
id: 'unknown-tool-1',
name: 'Unknown Tool',
type: 'n8n-nodes-base.nonExistentNodeTool',
typeVersion: 1,
position: [250, 300] as [number, number],
parameters: {}
}
],
connections: {}
};
const result = await validator.validateWorkflow(workflow);
// Should have "Unknown node type" error
const unknownErrors = result.errors.filter(e =>
e.message && e.message.includes('Unknown node type')
);
expect(unknownErrors).toHaveLength(1);
// Should NOT have INFERRED_TOOL_VARIANT warning
const inferredWarnings = result.warnings.filter(e =>
(e as any).code === 'INFERRED_TOOL_VARIANT'
);
expect(inferredWarnings).toHaveLength(0);
});
it('should handle multiple inferred tool variants in same workflow', async () => {
const workflow = {
nodes: [
{
id: 'drive-tool-1',
name: 'Google Drive Tool',
type: 'n8n-nodes-base.googleDriveTool',
typeVersion: 3,
position: [250, 300] as [number, number],
parameters: {}
},
{
id: 'sheets-tool-1',
name: 'Google Sheets Tool',
type: 'n8n-nodes-base.googleSheetsTool',
typeVersion: 4,
position: [250, 400] as [number, number],
parameters: {}
},
{
id: 'agent-1',
name: 'AI Agent',
type: '@n8n/n8n-nodes-langchain.agent',
typeVersion: 1.7,
position: [450, 300] as [number, number],
parameters: {}
}
],
connections: {
'Google Drive Tool': {
ai_tool: [[{ node: 'AI Agent', type: 'ai_tool', index: 0 }]]
},
'Google Sheets Tool': {
ai_tool: [[{ node: 'AI Agent', type: 'ai_tool', index: 0 }]]
}
}
};
const result = await validator.validateWorkflow(workflow);
// Should NOT have "Unknown node type" errors
const unknownErrors = result.errors.filter(e =>
e.message && e.message.includes('Unknown node type')
);
expect(unknownErrors).toHaveLength(0);
// Should have 2 INFERRED_TOOL_VARIANT warnings
const inferredWarnings = result.warnings.filter(e =>
(e as any).code === 'INFERRED_TOOL_VARIANT'
);
expect(inferredWarnings).toHaveLength(2);
});
it('should prefer database record over inference for supabaseTool', async () => {
const workflow = {
nodes: [
{
id: 'supabase-tool-1',
name: 'Supabase Tool',
type: 'n8n-nodes-base.supabaseTool',
typeVersion: 1,
position: [250, 300] as [number, number],
parameters: {}
}
],
connections: {}
};
const result = await validator.validateWorkflow(workflow);
// Should NOT have "Unknown node type" error
const unknownErrors = result.errors.filter(e =>
e.message && e.message.includes('Unknown node type')
);
expect(unknownErrors).toHaveLength(0);
// Should NOT have INFERRED_TOOL_VARIANT warning (it's in database)
const inferredWarnings = result.warnings.filter(e =>
(e as any).code === 'INFERRED_TOOL_VARIANT'
);
expect(inferredWarnings).toHaveLength(0);
});
it('should include helpful message in warning', async () => {
const workflow = {
nodes: [
{
id: 'drive-tool-1',
name: 'Google Drive Tool',
type: 'n8n-nodes-base.googleDriveTool',
typeVersion: 3,
position: [250, 300] as [number, number],
parameters: {}
}
],
connections: {}
};
const result = await validator.validateWorkflow(workflow);
const inferredWarning = result.warnings.find(e =>
(e as any).code === 'INFERRED_TOOL_VARIANT'
);
expect(inferredWarning).toBeDefined();
expect(inferredWarning!.message).toContain('inferred as a dynamic AI Tool variant');
expect(inferredWarning!.message).toContain('nodes-base.googleDrive');
expect(inferredWarning!.message).toContain('Google Drive');
expect(inferredWarning!.message).toContain('AI Agent');
});
});
});

View File

@@ -1,7 +1,9 @@
import { describe, it, expect, beforeEach, vi, afterEach, beforeAll, afterAll, type MockInstance } from 'vitest';
import { TelemetryBatchProcessor } from '../../../src/telemetry/batch-processor';
import { TelemetryEvent, WorkflowTelemetry, TELEMETRY_CONFIG } from '../../../src/telemetry/telemetry-types';
import { TelemetryEvent, WorkflowTelemetry, WorkflowMutationRecord, TELEMETRY_CONFIG } from '../../../src/telemetry/telemetry-types';
import { TelemetryError, TelemetryErrorType } from '../../../src/telemetry/telemetry-error';
import { IntentClassification, MutationToolName } from '../../../src/telemetry/mutation-types';
import { AddNodeOperation } from '../../../src/types/workflow-diff';
import type { SupabaseClient } from '@supabase/supabase-js';
// Mock logger to avoid console output in tests
@@ -679,4 +681,258 @@ describe('TelemetryBatchProcessor', () => {
expect(mockProcessExit).toHaveBeenCalledWith(0);
});
});
describe('Issue #517: workflow data preservation', () => {
// This test verifies that workflow mutation data is NOT recursively converted to snake_case
// Previously, the toSnakeCase function was applied recursively which caused:
// - Connection keys like "Webhook" to become "_webhook"
// - Node fields like "typeVersion" to become "type_version"
it('should preserve connection keys exactly as-is (node names)', async () => {
const mutation: WorkflowMutationRecord = {
userId: 'user1',
sessionId: 'session1',
workflowBefore: {
nodes: [],
connections: {}
},
workflowAfter: {
nodes: [
{ id: '1', name: 'Webhook', type: 'n8n-nodes-base.webhook', typeVersion: 1, position: [0, 0], parameters: {} }
],
// Connection keys are NODE NAMES - must be preserved exactly
connections: {
'Webhook': { main: [[{ node: 'AI Agent', type: 'main', index: 0 }]] },
'AI Agent': { main: [[{ node: 'HTTP Request', type: 'main', index: 0 }]] },
'HTTP Request': { main: [[{ node: 'Send Email', type: 'main', index: 0 }]] }
}
},
workflowHashBefore: 'hash1',
workflowHashAfter: 'hash2',
userIntent: 'Test',
intentClassification: IntentClassification.ADD_FUNCTIONALITY,
toolName: MutationToolName.UPDATE_PARTIAL,
operations: [],
operationCount: 0,
operationTypes: [],
validationImproved: null,
errorsResolved: 0,
errorsIntroduced: 0,
nodesAdded: 1,
nodesRemoved: 0,
nodesModified: 0,
connectionsAdded: 3,
connectionsRemoved: 0,
propertiesChanged: 0,
mutationSuccess: true,
durationMs: 100
};
let capturedData: any = null;
vi.mocked(mockSupabase.from).mockImplementation((table) => ({
insert: vi.fn().mockImplementation((data) => {
if (table === 'workflow_mutations') {
capturedData = data;
}
return Promise.resolve(createMockSupabaseResponse());
}),
url: { href: '' },
headers: {},
select: vi.fn(),
upsert: vi.fn(),
update: vi.fn(),
delete: vi.fn()
} as any));
await batchProcessor.flush(undefined, undefined, [mutation]);
expect(capturedData).toBeDefined();
expect(capturedData).toHaveLength(1);
const savedMutation = capturedData[0];
// Top-level keys should be snake_case for Supabase
expect(savedMutation).toHaveProperty('user_id');
expect(savedMutation).toHaveProperty('session_id');
expect(savedMutation).toHaveProperty('workflow_after');
// Connection keys should be preserved EXACTLY (not "_webhook", "_a_i _agent", etc.)
const connections = savedMutation.workflow_after.connections;
expect(connections).toHaveProperty('Webhook'); // NOT "_webhook"
expect(connections).toHaveProperty('AI Agent'); // NOT "_a_i _agent"
expect(connections).toHaveProperty('HTTP Request'); // NOT "_h_t_t_p _request"
});
it('should preserve node field names in camelCase', async () => {
const mutation: WorkflowMutationRecord = {
userId: 'user1',
sessionId: 'session1',
workflowBefore: { nodes: [], connections: {} },
workflowAfter: {
nodes: [
{
id: '1',
name: 'Webhook',
type: 'n8n-nodes-base.webhook',
// These fields MUST remain in camelCase for n8n API compatibility
typeVersion: 2,
webhookId: 'abc123',
onError: 'continueOnFail',
alwaysOutputData: true,
continueOnFail: false,
retryOnFail: true,
maxTries: 3,
notesInFlow: true,
waitBetweenTries: 1000,
executeOnce: false,
position: [100, 200],
parameters: {}
}
],
connections: {}
},
workflowHashBefore: 'hash1',
workflowHashAfter: 'hash2',
userIntent: 'Test',
intentClassification: IntentClassification.ADD_FUNCTIONALITY,
toolName: MutationToolName.UPDATE_PARTIAL,
operations: [],
operationCount: 0,
operationTypes: [],
validationImproved: null,
errorsResolved: 0,
errorsIntroduced: 0,
nodesAdded: 1,
nodesRemoved: 0,
nodesModified: 0,
connectionsAdded: 0,
connectionsRemoved: 0,
propertiesChanged: 0,
mutationSuccess: true,
durationMs: 100
};
let capturedData: any = null;
vi.mocked(mockSupabase.from).mockImplementation((table) => ({
insert: vi.fn().mockImplementation((data) => {
if (table === 'workflow_mutations') {
capturedData = data;
}
return Promise.resolve(createMockSupabaseResponse());
}),
url: { href: '' },
headers: {},
select: vi.fn(),
upsert: vi.fn(),
update: vi.fn(),
delete: vi.fn()
} as any));
await batchProcessor.flush(undefined, undefined, [mutation]);
expect(capturedData).toBeDefined();
const savedNode = capturedData[0].workflow_after.nodes[0];
// Node fields should be preserved in camelCase (NOT snake_case)
expect(savedNode).toHaveProperty('typeVersion'); // NOT type_version
expect(savedNode).toHaveProperty('webhookId'); // NOT webhook_id
expect(savedNode).toHaveProperty('onError'); // NOT on_error
expect(savedNode).toHaveProperty('alwaysOutputData'); // NOT always_output_data
expect(savedNode).toHaveProperty('continueOnFail'); // NOT continue_on_fail
expect(savedNode).toHaveProperty('retryOnFail'); // NOT retry_on_fail
expect(savedNode).toHaveProperty('maxTries'); // NOT max_tries
expect(savedNode).toHaveProperty('notesInFlow'); // NOT notes_in_flow
expect(savedNode).toHaveProperty('waitBetweenTries'); // NOT wait_between_tries
expect(savedNode).toHaveProperty('executeOnce'); // NOT execute_once
// Verify values are preserved
expect(savedNode.typeVersion).toBe(2);
expect(savedNode.webhookId).toBe('abc123');
expect(savedNode.maxTries).toBe(3);
});
it('should convert only top-level mutation record fields to snake_case', async () => {
const mutation: WorkflowMutationRecord = {
userId: 'user1',
sessionId: 'session1',
workflowBefore: { nodes: [], connections: {} },
workflowAfter: { nodes: [], connections: {} },
workflowHashBefore: 'hash1',
workflowHashAfter: 'hash2',
workflowStructureHashBefore: 'struct1',
workflowStructureHashAfter: 'struct2',
isTrulySuccessful: true,
userIntent: 'Test intent',
intentClassification: IntentClassification.ADD_FUNCTIONALITY,
toolName: MutationToolName.UPDATE_PARTIAL,
operations: [{ type: 'addNode', node: { name: 'Test', type: 'n8n-nodes-base.set', position: [0, 0] } } as AddNodeOperation],
operationCount: 1,
operationTypes: ['addNode'],
validationBefore: { valid: false, errors: [] },
validationAfter: { valid: true, errors: [] },
validationImproved: true,
errorsResolved: 1,
errorsIntroduced: 0,
nodesAdded: 1,
nodesRemoved: 0,
nodesModified: 0,
connectionsAdded: 0,
connectionsRemoved: 0,
propertiesChanged: 0,
mutationSuccess: true,
mutationError: undefined,
durationMs: 150
};
let capturedData: any = null;
vi.mocked(mockSupabase.from).mockImplementation((table) => ({
insert: vi.fn().mockImplementation((data) => {
if (table === 'workflow_mutations') {
capturedData = data;
}
return Promise.resolve(createMockSupabaseResponse());
}),
url: { href: '' },
headers: {},
select: vi.fn(),
upsert: vi.fn(),
update: vi.fn(),
delete: vi.fn()
} as any));
await batchProcessor.flush(undefined, undefined, [mutation]);
expect(capturedData).toBeDefined();
const saved = capturedData[0];
// Top-level fields should be converted to snake_case
expect(saved).toHaveProperty('user_id', 'user1');
expect(saved).toHaveProperty('session_id', 'session1');
expect(saved).toHaveProperty('workflow_before');
expect(saved).toHaveProperty('workflow_after');
expect(saved).toHaveProperty('workflow_hash_before', 'hash1');
expect(saved).toHaveProperty('workflow_hash_after', 'hash2');
expect(saved).toHaveProperty('workflow_structure_hash_before', 'struct1');
expect(saved).toHaveProperty('workflow_structure_hash_after', 'struct2');
expect(saved).toHaveProperty('is_truly_successful', true);
expect(saved).toHaveProperty('user_intent', 'Test intent');
expect(saved).toHaveProperty('intent_classification');
expect(saved).toHaveProperty('tool_name');
expect(saved).toHaveProperty('operation_count', 1);
expect(saved).toHaveProperty('operation_types');
expect(saved).toHaveProperty('validation_before');
expect(saved).toHaveProperty('validation_after');
expect(saved).toHaveProperty('validation_improved', true);
expect(saved).toHaveProperty('errors_resolved', 1);
expect(saved).toHaveProperty('errors_introduced', 0);
expect(saved).toHaveProperty('nodes_added', 1);
expect(saved).toHaveProperty('nodes_removed', 0);
expect(saved).toHaveProperty('nodes_modified', 0);
expect(saved).toHaveProperty('connections_added', 0);
expect(saved).toHaveProperty('connections_removed', 0);
expect(saved).toHaveProperty('properties_changed', 0);
expect(saved).toHaveProperty('mutation_success', true);
expect(saved).toHaveProperty('duration_ms', 150);
});
});
});