mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-03-27 12:43:12 +00:00
Compare commits
13 Commits
update/n8n
...
refactor/s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f8d61ed54 | ||
|
|
254996b51a | ||
|
|
132a765a0c | ||
|
|
fdc37efde7 | ||
|
|
07bd1d4cc2 | ||
|
|
1f0738e637 | ||
|
|
93816fce30 | ||
|
|
ec19c9dade | ||
|
|
6f6668acc4 | ||
|
|
c5665632af | ||
|
|
be3d07dbdc | ||
|
|
47a1cb135d | ||
|
|
14962a39b6 |
176
.github/workflows/benchmark-pr.yml
vendored
176
.github/workflows/benchmark-pr.yml
vendored
@@ -1,176 +0,0 @@
|
||||
name: Benchmark PR Comparison
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '**.txt'
|
||||
- 'docs/**'
|
||||
- 'examples/**'
|
||||
- '.github/FUNDING.yml'
|
||||
- '.github/ISSUE_TEMPLATE/**'
|
||||
- '.github/pull_request_template.md'
|
||||
- '.gitignore'
|
||||
- 'LICENSE*'
|
||||
- 'ATTRIBUTION.md'
|
||||
- 'SECURITY.md'
|
||||
- 'CODE_OF_CONDUCT.md'
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: read
|
||||
statuses: write
|
||||
|
||||
jobs:
|
||||
benchmark-comparison:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout PR branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
# Run benchmarks on current branch
|
||||
- name: Run current benchmarks
|
||||
run: npm run benchmark:ci
|
||||
|
||||
- name: Save current results
|
||||
run: cp benchmark-results.json benchmark-current.json
|
||||
|
||||
# Checkout and run benchmarks on base branch
|
||||
- name: Checkout base branch
|
||||
run: |
|
||||
git checkout ${{ github.event.pull_request.base.sha }}
|
||||
git status
|
||||
|
||||
- name: Install base dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run baseline benchmarks
|
||||
run: npm run benchmark:ci
|
||||
continue-on-error: true
|
||||
|
||||
- name: Save baseline results
|
||||
run: |
|
||||
if [ -f benchmark-results.json ]; then
|
||||
cp benchmark-results.json benchmark-baseline.json
|
||||
else
|
||||
echo '{"files":[]}' > benchmark-baseline.json
|
||||
fi
|
||||
|
||||
# Compare results
|
||||
- name: Checkout PR branch again
|
||||
run: git checkout ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Compare benchmarks
|
||||
id: compare
|
||||
run: |
|
||||
node scripts/compare-benchmarks.js benchmark-current.json benchmark-baseline.json || echo "REGRESSION=true" >> $GITHUB_OUTPUT
|
||||
|
||||
# Upload comparison artifacts
|
||||
- name: Upload benchmark comparison
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: benchmark-comparison-${{ github.run_number }}
|
||||
path: |
|
||||
benchmark-current.json
|
||||
benchmark-baseline.json
|
||||
benchmark-comparison.json
|
||||
benchmark-comparison.md
|
||||
retention-days: 30
|
||||
|
||||
# Post comparison to PR
|
||||
- name: Post benchmark comparison to PR
|
||||
if: always()
|
||||
uses: actions/github-script@v7
|
||||
continue-on-error: true
|
||||
with:
|
||||
script: |
|
||||
try {
|
||||
const fs = require('fs');
|
||||
let comment = '## ⚡ Benchmark Comparison\n\n';
|
||||
|
||||
try {
|
||||
if (fs.existsSync('benchmark-comparison.md')) {
|
||||
const comparison = fs.readFileSync('benchmark-comparison.md', 'utf8');
|
||||
comment += comparison;
|
||||
} else {
|
||||
comment += 'Benchmark comparison could not be generated.';
|
||||
}
|
||||
} catch (error) {
|
||||
comment += `Error reading benchmark comparison: ${error.message}`;
|
||||
}
|
||||
|
||||
comment += '\n\n---\n';
|
||||
comment += `*[View full benchmark results](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})*`;
|
||||
|
||||
// Find existing comment
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
});
|
||||
|
||||
const botComment = comments.find(comment =>
|
||||
comment.user.type === 'Bot' &&
|
||||
comment.body.includes('## ⚡ Benchmark Comparison')
|
||||
);
|
||||
|
||||
if (botComment) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: botComment.id,
|
||||
body: comment
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: comment
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to create/update PR comment:', error.message);
|
||||
console.log('This is likely due to insufficient permissions for external PRs.');
|
||||
console.log('Benchmark comparison has been saved to artifacts instead.');
|
||||
}
|
||||
|
||||
# Add status check
|
||||
- name: Set benchmark status
|
||||
if: always()
|
||||
uses: actions/github-script@v7
|
||||
continue-on-error: true
|
||||
with:
|
||||
script: |
|
||||
try {
|
||||
const hasRegression = '${{ steps.compare.outputs.REGRESSION }}' === 'true';
|
||||
const state = hasRegression ? 'failure' : 'success';
|
||||
const description = hasRegression
|
||||
? 'Performance regressions detected'
|
||||
: 'No performance regressions';
|
||||
|
||||
await github.rest.repos.createCommitStatus({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
sha: context.sha,
|
||||
state: state,
|
||||
target_url: `https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}`,
|
||||
description: description,
|
||||
context: 'benchmarks/regression-check'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to create commit status:', error.message);
|
||||
console.log('This is likely due to insufficient permissions for external PRs.');
|
||||
}
|
||||
214
.github/workflows/benchmark.yml
vendored
214
.github/workflows/benchmark.yml
vendored
@@ -1,214 +0,0 @@
|
||||
name: Performance Benchmarks
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, feat/comprehensive-testing-suite]
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '**.txt'
|
||||
- 'docs/**'
|
||||
- 'examples/**'
|
||||
- '.github/FUNDING.yml'
|
||||
- '.github/ISSUE_TEMPLATE/**'
|
||||
- '.github/pull_request_template.md'
|
||||
- '.gitignore'
|
||||
- 'LICENSE*'
|
||||
- 'ATTRIBUTION.md'
|
||||
- 'SECURITY.md'
|
||||
- 'CODE_OF_CONDUCT.md'
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '**.txt'
|
||||
- 'docs/**'
|
||||
- 'examples/**'
|
||||
- '.github/FUNDING.yml'
|
||||
- '.github/ISSUE_TEMPLATE/**'
|
||||
- '.github/pull_request_template.md'
|
||||
- '.gitignore'
|
||||
- 'LICENSE*'
|
||||
- 'ATTRIBUTION.md'
|
||||
- 'SECURITY.md'
|
||||
- 'CODE_OF_CONDUCT.md'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
# For PR comments
|
||||
pull-requests: write
|
||||
# For pushing to gh-pages branch
|
||||
contents: write
|
||||
# For deployment to GitHub Pages
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
benchmark:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# Fetch all history for proper benchmark comparison
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build project
|
||||
run: npm run build
|
||||
|
||||
- name: Run benchmarks
|
||||
run: npm run benchmark:ci
|
||||
|
||||
- name: Format benchmark results
|
||||
run: node scripts/format-benchmark-results.js
|
||||
|
||||
- name: Upload benchmark artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: benchmark-results
|
||||
path: |
|
||||
benchmark-results.json
|
||||
benchmark-results-formatted.json
|
||||
benchmark-summary.json
|
||||
|
||||
# Ensure gh-pages branch exists
|
||||
- name: Check and create gh-pages branch
|
||||
run: |
|
||||
git fetch origin gh-pages:gh-pages 2>/dev/null || {
|
||||
echo "gh-pages branch doesn't exist. Creating it..."
|
||||
git checkout --orphan gh-pages
|
||||
git rm -rf .
|
||||
echo "# Benchmark Results" > README.md
|
||||
git add README.md
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git commit -m "Initial gh-pages commit"
|
||||
git push origin gh-pages
|
||||
git checkout ${{ github.ref_name }}
|
||||
}
|
||||
|
||||
# Clean up workspace before benchmark action
|
||||
- name: Clean workspace
|
||||
run: |
|
||||
git add -A
|
||||
git stash || true
|
||||
|
||||
# Store benchmark results and compare
|
||||
- name: Store benchmark result
|
||||
uses: benchmark-action/github-action-benchmark@v1
|
||||
continue-on-error: true
|
||||
id: benchmark
|
||||
with:
|
||||
name: n8n-mcp Benchmarks
|
||||
tool: 'customSmallerIsBetter'
|
||||
output-file-path: benchmark-results-formatted.json
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
auto-push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
# Where to store benchmark data
|
||||
benchmark-data-dir-path: 'benchmarks'
|
||||
# Alert when performance regresses by 10%
|
||||
alert-threshold: '110%'
|
||||
# Comment on PR when regression is detected
|
||||
comment-on-alert: true
|
||||
alert-comment-cc-users: '@czlonkowski'
|
||||
# Summary always
|
||||
summary-always: true
|
||||
# Max number of data points to retain
|
||||
max-items-in-chart: 50
|
||||
fail-on-alert: false
|
||||
|
||||
# Comment on PR with benchmark results
|
||||
- name: Comment PR with results
|
||||
uses: actions/github-script@v7
|
||||
if: github.event_name == 'pull_request'
|
||||
continue-on-error: true
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
try {
|
||||
const fs = require('fs');
|
||||
const summary = JSON.parse(fs.readFileSync('benchmark-summary.json', 'utf8'));
|
||||
|
||||
// Format results for PR comment
|
||||
let comment = '## 📊 Performance Benchmark Results\n\n';
|
||||
comment += `🕐 Run at: ${new Date(summary.timestamp).toLocaleString()}\n\n`;
|
||||
comment += '| Benchmark | Time | Ops/sec | Range |\n';
|
||||
comment += '|-----------|------|---------|-------|\n';
|
||||
|
||||
// Group benchmarks by category
|
||||
const categories = {};
|
||||
for (const benchmark of summary.benchmarks) {
|
||||
const [category, ...nameParts] = benchmark.name.split(' - ');
|
||||
if (!categories[category]) categories[category] = [];
|
||||
categories[category].push({
|
||||
...benchmark,
|
||||
shortName: nameParts.join(' - ')
|
||||
});
|
||||
}
|
||||
|
||||
// Display by category
|
||||
for (const [category, benchmarks] of Object.entries(categories)) {
|
||||
comment += `\n### ${category}\n`;
|
||||
for (const benchmark of benchmarks) {
|
||||
comment += `| ${benchmark.shortName} | ${benchmark.time} | ${benchmark.opsPerSec} | ${benchmark.range} |\n`;
|
||||
}
|
||||
}
|
||||
|
||||
// Add comparison link
|
||||
comment += '\n\n📈 [View historical benchmark trends](https://czlonkowski.github.io/n8n-mcp/benchmarks/)\n';
|
||||
comment += '\n⚡ Performance regressions >10% will be flagged automatically.\n';
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: comment
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to create PR comment:', error.message);
|
||||
console.log('This is likely due to insufficient permissions for external PRs.');
|
||||
console.log('Benchmark results have been saved to artifacts instead.');
|
||||
}
|
||||
|
||||
# Deploy benchmark results to GitHub Pages
|
||||
deploy:
|
||||
needs: benchmark
|
||||
if: github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: gh-pages
|
||||
continue-on-error: true
|
||||
|
||||
# If gh-pages checkout failed, create a minimal structure
|
||||
- name: Ensure gh-pages content exists
|
||||
run: |
|
||||
if [ ! -f "index.html" ]; then
|
||||
echo "Creating minimal gh-pages structure..."
|
||||
mkdir -p benchmarks
|
||||
echo '<!DOCTYPE html><html><head><title>n8n-mcp Benchmarks</title></head><body><h1>n8n-mcp Benchmarks</h1><p>Benchmark data will appear here after the first run.</p></body></html>' > index.html
|
||||
fi
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v4
|
||||
|
||||
- name: Upload Pages artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: '.'
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
6
.github/workflows/dependency-check.yml
vendored
6
.github/workflows/dependency-check.yml
vendored
@@ -77,15 +77,15 @@ jobs:
|
||||
echo "Zod version: $ZOD_VERSION"
|
||||
echo ""
|
||||
|
||||
# Check MCP SDK version - must be exactly 1.27.1
|
||||
# Check MCP SDK version - must be exactly 1.28.0
|
||||
if [[ "$SDK_VERSION" == "not found" ]]; then
|
||||
echo "❌ FAILED: Could not determine MCP SDK version!"
|
||||
echo " The dependency may not have been installed correctly."
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$SDK_VERSION" != "1.27.1" ]]; then
|
||||
if [[ "$SDK_VERSION" != "1.28.0" ]]; then
|
||||
echo "❌ FAILED: MCP SDK version mismatch!"
|
||||
echo " Expected: 1.27.1"
|
||||
echo " Expected: 1.28.0"
|
||||
echo " Got: $SDK_VERSION"
|
||||
echo ""
|
||||
echo "This can cause runtime errors. See issues #440, #444, #446, #447, #450"
|
||||
|
||||
102
.github/workflows/test.yml
vendored
102
.github/workflows/test.yml
vendored
@@ -133,23 +133,6 @@ jobs:
|
||||
- name: Run type checking
|
||||
run: npm run typecheck
|
||||
|
||||
# Run benchmarks
|
||||
- name: Run benchmarks
|
||||
id: benchmarks
|
||||
run: npm run benchmark:ci
|
||||
continue-on-error: true
|
||||
|
||||
# Upload benchmark results
|
||||
- name: Upload benchmark results
|
||||
if: always() && steps.benchmarks.outcome != 'skipped'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: benchmark-results-${{ github.run_number }}-${{ github.run_attempt }}
|
||||
path: |
|
||||
benchmark-results.json
|
||||
retention-days: 30
|
||||
if-no-files-found: warn
|
||||
|
||||
# Create test report comment for PRs
|
||||
- name: Create test report comment
|
||||
if: github.event_name == 'pull_request' && always()
|
||||
@@ -222,7 +205,6 @@ jobs:
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- [Test Results](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- [Coverage Report](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- [Benchmark Results](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Store test metadata
|
||||
- name: Store test metadata
|
||||
@@ -252,24 +234,21 @@ jobs:
|
||||
path: test-metadata.json
|
||||
retention-days: 30
|
||||
|
||||
# Separate job to process and publish test results
|
||||
# Publish test results as checks
|
||||
publish-results:
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
if: always()
|
||||
permissions:
|
||||
checks: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# Download all artifacts
|
||||
- name: Download all artifacts
|
||||
|
||||
- name: Download test results
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
# Publish test results as checks
|
||||
|
||||
- name: Publish test results
|
||||
uses: dorny/test-reporter@v1
|
||||
if: always()
|
||||
@@ -279,75 +258,4 @@ jobs:
|
||||
path: 'artifacts/test-results-*/test-results/junit.xml'
|
||||
reporter: java-junit
|
||||
fail-on-error: false
|
||||
fail-on-empty: false
|
||||
|
||||
# Create a combined artifact with all results
|
||||
- name: Create combined results artifact
|
||||
if: always()
|
||||
run: |
|
||||
mkdir -p combined-results
|
||||
cp -r artifacts/* combined-results/ 2>/dev/null || true
|
||||
|
||||
# Create index file
|
||||
cat > combined-results/index.html << 'EOF'
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>n8n-mcp Test Results</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 40px; }
|
||||
h1 { color: #333; }
|
||||
.section { margin: 20px 0; padding: 20px; border: 1px solid #ddd; border-radius: 5px; }
|
||||
a { color: #0066cc; text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>n8n-mcp Test Results</h1>
|
||||
<div class="section">
|
||||
<h2>Test Reports</h2>
|
||||
<ul>
|
||||
<li><a href="test-results-${{ github.run_number }}-${{ github.run_attempt }}/test-reports/report.html">📊 Detailed HTML Report</a></li>
|
||||
<li><a href="test-results-${{ github.run_number }}-${{ github.run_attempt }}/test-results/html/index.html">📈 Vitest HTML Report</a></li>
|
||||
<li><a href="test-results-${{ github.run_number }}-${{ github.run_attempt }}/test-reports/report.md">📄 Markdown Report</a></li>
|
||||
<li><a href="test-results-${{ github.run_number }}-${{ github.run_attempt }}/test-summary.md">📝 PR Summary</a></li>
|
||||
<li><a href="test-results-${{ github.run_number }}-${{ github.run_attempt }}/test-results/junit.xml">🔧 JUnit XML</a></li>
|
||||
<li><a href="test-results-${{ github.run_number }}-${{ github.run_attempt }}/test-results/results.json">🔢 JSON Results</a></li>
|
||||
<li><a href="test-results-${{ github.run_number }}-${{ github.run_attempt }}/test-reports/report.json">📊 Full JSON Report</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h2>Coverage Reports</h2>
|
||||
<ul>
|
||||
<li><a href="coverage-${{ github.run_number }}-${{ github.run_attempt }}/html/index.html">HTML Coverage Report</a></li>
|
||||
<li><a href="coverage-${{ github.run_number }}-${{ github.run_attempt }}/lcov.info">LCOV Report</a></li>
|
||||
<li><a href="coverage-${{ github.run_number }}-${{ github.run_attempt }}/coverage-summary.json">Coverage Summary JSON</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h2>Benchmark Results</h2>
|
||||
<ul>
|
||||
<li><a href="benchmark-results-${{ github.run_number }}-${{ github.run_attempt }}/benchmark-results.json">Benchmark Results JSON</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h2>Metadata</h2>
|
||||
<ul>
|
||||
<li><a href="test-metadata-${{ github.run_number }}-${{ github.run_attempt }}/test-metadata.json">Test Run Metadata</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<p><em>Generated at $(date -u +%Y-%m-%dT%H:%M:%SZ)</em></p>
|
||||
<p><em>Run: #${{ github.run_number }} | SHA: ${{ github.sha }}</em></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
|
||||
- name: Upload combined results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: all-test-results-${{ github.run_number }}
|
||||
path: combined-results/
|
||||
retention-days: 90
|
||||
fail-on-empty: false
|
||||
96
CHANGELOG.md
96
CHANGELOG.md
@@ -7,6 +7,102 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2.41.0] - 2026-03-25
|
||||
|
||||
### Changed
|
||||
|
||||
- **Updated n8n dependencies**: n8n 2.12.3 → 2.13.3, n8n-core 2.12.0 → 2.13.1, n8n-workflow 2.12.0 → 2.13.1, @n8n/n8n-nodes-langchain 2.12.0 → 2.13.1
|
||||
- **Rebuilt node database**: 1,396 nodes (812 from n8n-nodes-base/langchain + 584 community: 516 verified + 68 npm)
|
||||
- **Refreshed community nodes**: 584 total (up from 430), with 581 AI-generated documentation summaries
|
||||
- **Improved documentation generator**: Strip `<think>` tags from thinking-model responses; use raw fetch for vLLM `chat_template_kwargs` support
|
||||
- **Incremental community node updates**: `fetch:community` now upserts by default, preserving existing READMEs and AI summaries. Use `--rebuild` for clean slate
|
||||
|
||||
Conceived by Romuald Czlonkowski - https://www.aiadvisors.pl/en
|
||||
|
||||
## [2.40.5] - 2026-03-22
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Webhook workflows created via MCP get 404 errors** (Issue #643): Auto-inject `webhookId` (UUID) on webhook-type nodes (`webhook`, `webhookTrigger`, `formTrigger`, `chatTrigger`) during `cleanWorkflowForCreate()` and `cleanWorkflowForUpdate()`. n8n 2.10+ requires this field for proper webhook URL registration; without it, webhooks silently fail with 404. Existing `webhookId` values are preserved.
|
||||
|
||||
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
|
||||
|
||||
## [2.40.4] - 2026-03-22
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Incorrect data tables availability info**: Removed "enterprise/cloud only" restriction from tool description and documentation — data tables are available on all n8n plans including self-hosted
|
||||
- **Redundant pitfalls removed**: Removed "Requires N8N_API_URL and N8N_API_KEY" and "enterprise or cloud plans" pitfalls — the first is implicit for all n8n management tools, the second was incorrect
|
||||
|
||||
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
|
||||
|
||||
## [2.40.3] - 2026-03-22
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Notification 400 disconnect storms (#654)**: `handleRequest()` now returns 202 Accepted for JSON-RPC notifications with stale/expired session IDs instead of 400. Per JSON-RPC 2.0 spec, notifications don't expect responses — returning 400 caused Claude's proxy to trigger reconnection storms (930 errors/day, 216 users affected)
|
||||
- **TOCTOU race in session lookup**: Added null guard after transport assignment to handle sessions removed between the existence check and use
|
||||
- **`updateTable` silently ignoring `columns` parameter**: Now returns a warning message when `columns` is passed to `updateTable`, clarifying that table schema is immutable after creation via the public API
|
||||
- **Tool schema descriptions clarified**: `name` and `columns` parameter descriptions now explicitly document that `updateTable` is rename-only and columns are for `createTable` only
|
||||
|
||||
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
|
||||
|
||||
## [2.40.2] - 2026-03-22
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Double URL-encoding of `filter` and `sortBy` in `getRows`/`deleteRows`**: Moved `encodeURIComponent()` from handler layer to a custom `paramsSerializer` in the API client. Handlers were encoding values before passing them as Axios params, causing double-encoding (`%257B` instead of `%7B`). Handlers now pass raw values; the API client encodes once via `serializeDataTableParams()`
|
||||
- **`updateTable` documentation clarified**: Explicitly notes that only renaming is supported (no column modifications via public API)
|
||||
|
||||
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
|
||||
|
||||
## [2.40.1] - 2026-03-21
|
||||
|
||||
### Fixed
|
||||
|
||||
- **`n8n_manage_datatable` row operations broken by MCP transport serialization**: `data` parameter received as string instead of JSON — added `z.preprocess` coercers for array/object/filter params
|
||||
- **`n8n_manage_datatable` filter/sortBy URL encoding**: n8n API requires URL-encoded query params — added `encodeURIComponent()` for filter and sortBy in getRows and deleteRows (revised in 2.40.2 to move encoding to API client layer)
|
||||
- **`json` column type rejected by n8n API**: Removed `json` from column type enum (n8n only accepts string/number/boolean/date)
|
||||
- **Garbled 404 error messages**: Fixed `N8nNotFoundError` constructor — API error messages are now passed through cleanly instead of being wrapped in "Resource with ID ... not found"
|
||||
|
||||
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
|
||||
|
||||
## [2.40.0] - 2026-03-21
|
||||
|
||||
### Changed
|
||||
|
||||
- **`n8n_manage_datatable` MCP tool** (replaces `n8n_create_data_table`): Full data table management covering all 10 n8n data table API endpoints
|
||||
- **Table operations**: createTable, listTables, getTable, updateTable, deleteTable
|
||||
- **Row operations**: getRows, insertRows, updateRows, upsertRows, deleteRows
|
||||
- Filter system with and/or logic and 8 condition operators (eq, neq, like, ilike, gt, gte, lt, lte)
|
||||
- Dry-run support for updateRows, upsertRows, deleteRows
|
||||
- Pagination, sorting, and full-text search for row listing
|
||||
- Shared error handler and consolidated Zod schemas for consistency
|
||||
- 9 new `N8nApiClient` methods for all data table endpoints
|
||||
- **`projectId` parameter for `n8n_create_workflow`**: Create workflows directly in a specific team project (enterprise feature)
|
||||
|
||||
### Breaking
|
||||
|
||||
- `n8n_create_data_table` tool replaced by `n8n_manage_datatable` with `action: "createTable"`
|
||||
|
||||
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
|
||||
|
||||
## [2.38.0] - 2026-03-20
|
||||
|
||||
### Added
|
||||
|
||||
- **`transferWorkflow` diff operation** (Issue #644): Move workflows between projects via `n8n_update_partial_workflow`
|
||||
- New `transferWorkflow` operation type with `destinationProjectId` parameter
|
||||
- Calls `PUT /workflows/{id}/transfer` via dedicated API after workflow update
|
||||
- Proper error handling: returns `{ success: false, saved: true }` when transfer fails after update
|
||||
- Transfer executes before activation so workflow is in target project first
|
||||
- Zod schema validates `destinationProjectId` is non-empty
|
||||
- Updated tool description and documentation to list the new operation
|
||||
- `inferIntentFromOperations` returns descriptive intent for transfer operations
|
||||
- `N8nApiClient.transferWorkflow()` method added
|
||||
|
||||
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
|
||||
|
||||
## [2.37.4] - 2026-03-18
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
# n8n Update Process - Quick Reference
|
||||
|
||||
## ⚡ Recommended Fast Workflow (2025-11-04)
|
||||
## ⚡ Recommended Fast Workflow (2026-03-25)
|
||||
|
||||
**CRITICAL FIRST STEP**: Check existing releases to avoid version conflicts!
|
||||
|
||||
**IMPORTANT: Community nodes are preserved incrementally!**
|
||||
- `npm run update:n8n` rebuilds the base node DB (wipes community nodes temporarily)
|
||||
- Community nodes must be backed up BEFORE and restored AFTER the base rebuild
|
||||
- `npm run fetch:community` now upserts by default (preserves READMEs + AI summaries)
|
||||
- `npm run generate:docs:incremental` only processes nodes missing docs
|
||||
- Use `generate:docs:readme-only` first, then `generate:docs:summary-only` with a local LLM
|
||||
|
||||
```bash
|
||||
# 1. CHECK EXISTING RELEASES FIRST (prevents version conflicts!)
|
||||
gh release list | head -5
|
||||
@@ -15,14 +22,29 @@ git checkout main && git pull
|
||||
# 3. Check for updates (dry run)
|
||||
npm run update:n8n:check
|
||||
|
||||
# 4. Run update and skip tests (we'll test in CI)
|
||||
# 4. Back up community nodes BEFORE update (update:n8n rebuilds base DB!)
|
||||
sqlite3 data/nodes.db ".mode insert nodes" "SELECT * FROM nodes WHERE is_community = 1;" > /tmp/n8n_community_backup.sql
|
||||
|
||||
# 5. Run update and skip tests (we'll test in CI)
|
||||
yes y | npm run update:n8n
|
||||
|
||||
# 5. Refresh community nodes (standard practice!)
|
||||
npm run fetch:community
|
||||
npm run generate:docs
|
||||
# 6. Restore community nodes after rebuild
|
||||
sqlite3 data/nodes.db < /tmp/n8n_community_backup.sql
|
||||
|
||||
# 6. Create feature branch
|
||||
# 7. Refresh community nodes (upserts - preserves existing READMEs + AI summaries!)
|
||||
npm run fetch:community
|
||||
# NOTE: Default mode is now "upsert" - no deletion. Use --rebuild for clean slate.
|
||||
|
||||
# 8. Generate docs incrementally (only for new/missing nodes)
|
||||
npm run generate:docs:readme-only # Fetch READMEs from npm (no LLM needed)
|
||||
# Then with a local LLM server running (LM Studio, vLLM, Ollama):
|
||||
N8N_MCP_LLM_BASE_URL="http://YOUR_SERVER:PORT/v1" \
|
||||
N8N_MCP_LLM_MODEL="your-model-name" \
|
||||
node dist/scripts/generate-community-docs.js --summary-only --skip-existing-summary --llm-concurrency=11
|
||||
# For vLLM with thinking models, the code auto-sends chat_template_kwargs: {enable_thinking: false}
|
||||
# Context length needed: 8K minimum (README truncated to 6000 chars, output max 2000 tokens)
|
||||
|
||||
# 9. Create feature branch
|
||||
git checkout -b update/n8n-X.X.X
|
||||
|
||||
# 7. Update version in package.json (must be HIGHER than latest release!)
|
||||
|
||||
@@ -5,17 +5,17 @@
|
||||
[](https://www.npmjs.com/package/n8n-mcp)
|
||||
[](https://codecov.io/gh/czlonkowski/n8n-mcp)
|
||||
[](https://github.com/czlonkowski/n8n-mcp/actions)
|
||||
[](https://github.com/n8n-io/n8n)
|
||||
[](https://github.com/n8n-io/n8n)
|
||||
[](https://github.com/czlonkowski/n8n-mcp/pkgs/container/n8n-mcp)
|
||||
[](https://railway.com/deploy/n8n-mcp?referralCode=n8n-mcp)
|
||||
|
||||
A Model Context Protocol (MCP) server that provides AI assistants with comprehensive access to n8n node documentation, properties, and operations. Deploy in minutes to give Claude and other AI assistants deep knowledge about n8n's 1,239 workflow automation nodes (809 core + 430 community).
|
||||
A Model Context Protocol (MCP) server that provides AI assistants with comprehensive access to n8n node documentation, properties, and operations. Deploy in minutes to give Claude and other AI assistants deep knowledge about n8n's 1,396 workflow automation nodes (812 core + 584 community).
|
||||
|
||||
## Overview
|
||||
|
||||
n8n-MCP serves as a bridge between n8n's workflow automation platform and AI models, enabling them to understand and work with n8n nodes effectively. It provides structured access to:
|
||||
|
||||
- 📚 **1,084 n8n nodes** - 537 core nodes + 547 community nodes (301 verified)
|
||||
- 📚 **1,396 n8n nodes** - 812 core nodes + 584 community nodes (516 verified)
|
||||
- 🔧 **Node properties** - 99% coverage with detailed schemas
|
||||
- ⚡ **Node operations** - 63.6% coverage of available actions
|
||||
- 📄 **Documentation** - 87% coverage from official n8n docs (including AI nodes)
|
||||
|
||||
BIN
data/nodes.db
BIN
data/nodes.db
Binary file not shown.
2
dist/database/node-repository.d.ts.map
vendored
2
dist/database/node-repository.d.ts.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"node-repository.d.ts","sourceRoot":"","sources":["../../src/database/node-repository.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"node-repository.d.ts","sourceRoot":"","sources":["../../src/database/node-repository.ts"],"names":[],"mappings":"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"}
|
||||
8
dist/database/node-repository.js
vendored
8
dist/database/node-repository.js
vendored
@@ -12,6 +12,7 @@ class NodeRepository {
|
||||
this.db = dbOrService;
|
||||
}
|
||||
saveNode(node) {
|
||||
const existing = this.db.prepare('SELECT npm_readme, ai_documentation_summary, ai_summary_generated_at FROM nodes WHERE node_type = ?').get(node.nodeType);
|
||||
const stmt = this.db.prepare(`
|
||||
INSERT OR REPLACE INTO nodes (
|
||||
node_type, package_name, display_name, description,
|
||||
@@ -21,10 +22,11 @@ class NodeRepository {
|
||||
properties_schema, operations, credentials_required,
|
||||
outputs, output_names,
|
||||
is_community, is_verified, author_name, author_github_url,
|
||||
npm_package_name, npm_version, npm_downloads, community_fetched_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
npm_package_name, npm_version, npm_downloads, community_fetched_at,
|
||||
npm_readme, ai_documentation_summary, ai_summary_generated_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
stmt.run(node.nodeType, node.packageName, node.displayName, node.description, node.category, node.style, node.isAITool ? 1 : 0, node.isTrigger ? 1 : 0, node.isWebhook ? 1 : 0, node.isVersioned ? 1 : 0, node.isToolVariant ? 1 : 0, node.toolVariantOf || null, node.hasToolVariant ? 1 : 0, node.version, node.documentation || null, JSON.stringify(node.properties, null, 2), JSON.stringify(node.operations, null, 2), JSON.stringify(node.credentials, null, 2), node.outputs ? JSON.stringify(node.outputs, null, 2) : null, node.outputNames ? JSON.stringify(node.outputNames, null, 2) : null, node.isCommunity ? 1 : 0, node.isVerified ? 1 : 0, node.authorName || null, node.authorGithubUrl || null, node.npmPackageName || null, node.npmVersion || null, node.npmDownloads || 0, node.communityFetchedAt || null);
|
||||
stmt.run(node.nodeType, node.packageName, node.displayName, node.description, node.category, node.style, node.isAITool ? 1 : 0, node.isTrigger ? 1 : 0, node.isWebhook ? 1 : 0, node.isVersioned ? 1 : 0, node.isToolVariant ? 1 : 0, node.toolVariantOf || null, node.hasToolVariant ? 1 : 0, node.version, node.documentation || null, JSON.stringify(node.properties, null, 2), JSON.stringify(node.operations, null, 2), JSON.stringify(node.credentials, null, 2), node.outputs ? JSON.stringify(node.outputs, null, 2) : null, node.outputNames ? JSON.stringify(node.outputNames, null, 2) : null, node.isCommunity ? 1 : 0, node.isVerified ? 1 : 0, node.authorName || null, node.authorGithubUrl || null, node.npmPackageName || null, node.npmVersion || null, node.npmDownloads || 0, node.communityFetchedAt || null, existing?.npm_readme || null, existing?.ai_documentation_summary || null, existing?.ai_summary_generated_at || null);
|
||||
}
|
||||
getNode(nodeType) {
|
||||
const normalizedType = node_type_normalizer_1.NodeTypeNormalizer.normalizeToFullForm(nodeType);
|
||||
|
||||
2
dist/database/node-repository.js.map
vendored
2
dist/database/node-repository.js.map
vendored
File diff suppressed because one or more lines are too long
1
dist/http-server-single-session.d.ts
vendored
1
dist/http-server-single-session.d.ts
vendored
@@ -21,6 +21,7 @@ export declare class SingleSessionHTTPServer {
|
||||
private getActiveSessionCount;
|
||||
private canCreateSession;
|
||||
private isValidSessionId;
|
||||
private isJsonRpcNotification;
|
||||
private sanitizeErrorForClient;
|
||||
private updateSessionAccess;
|
||||
private switchSessionContext;
|
||||
|
||||
2
dist/http-server-single-session.d.ts.map
vendored
2
dist/http-server-single-session.d.ts.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"http-server-single-session.d.ts","sourceRoot":"","sources":["../src/http-server-single-session.ts"],"names":[],"mappings":";AAMA,OAAO,OAAO,MAAM,SAAS,CAAC;AAoB9B,OAAO,EAAE,eAAe,EAA2B,MAAM,0BAA0B,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAwErD,qBAAa,uBAAuB;IAElC,OAAO,CAAC,UAAU,CAA8D;IAChF,OAAO,CAAC,OAAO,CAA0D;IACzE,OAAO,CAAC,eAAe,CAAsE;IAC7F,OAAO,CAAC,eAAe,CAA4D;IACnF,OAAO,CAAC,kBAAkB,CAAyC;IACnE,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,cAAc,CAAwB;IAC9C,OAAO,CAAC,aAAa,CAAM;IAI3B,OAAO,CAAC,cAAc,CAER;IACd,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,YAAY,CAA+B;;IAcnD,OAAO,CAAC,mBAAmB;IAmB3B,OAAO,CAAC,sBAAsB;YAqChB,aAAa;IAuC3B,OAAO,CAAC,qBAAqB;IAO7B,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,sBAAsB;IAkC9B,OAAO,CAAC,mBAAmB;YASb,oBAAoB;YAwBpB,oBAAoB;IAwBlC,OAAO,CAAC,iBAAiB;IAsBzB,OAAO,CAAC,aAAa;IA2BrB,OAAO,CAAC,mBAAmB;IAoDrB,aAAa,CACjB,GAAG,EAAE,OAAO,CAAC,OAAO,EACpB,GAAG,EAAE,OAAO,CAAC,QAAQ,EACrB,eAAe,CAAC,EAAE,eAAe,GAChC,OAAO,CAAC,IAAI,CAAC;YA0PF,eAAe;IA4D7B,OAAO,CAAC,SAAS;IAYjB,OAAO,CAAC,gBAAgB;IASlB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAgnBtB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IA2D/B,cAAc,IAAI;QAChB,MAAM,EAAE,OAAO,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,QAAQ,CAAC,EAAE;YACT,KAAK,EAAE,MAAM,CAAC;YACd,MAAM,EAAE,MAAM,CAAC;YACf,OAAO,EAAE,MAAM,CAAC;YAChB,GAAG,EAAE,MAAM,CAAC;YACZ,UAAU,EAAE,MAAM,EAAE,CAAC;SACtB,CAAC;KACH;IAmDM,kBAAkB,IAAI,YAAY,EAAE;IAoEpC,mBAAmB,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,MAAM;CAsG7D"}
|
||||
{"version":3,"file":"http-server-single-session.d.ts","sourceRoot":"","sources":["../src/http-server-single-session.ts"],"names":[],"mappings":";AAMA,OAAO,OAAO,MAAM,SAAS,CAAC;AAoB9B,OAAO,EAAE,eAAe,EAA2B,MAAM,0BAA0B,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAwErD,qBAAa,uBAAuB;IAElC,OAAO,CAAC,UAAU,CAA8D;IAChF,OAAO,CAAC,OAAO,CAA0D;IACzE,OAAO,CAAC,eAAe,CAAsE;IAC7F,OAAO,CAAC,eAAe,CAA4D;IACnF,OAAO,CAAC,kBAAkB,CAAyC;IACnE,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,cAAc,CAAwB;IAC9C,OAAO,CAAC,aAAa,CAAM;IAI3B,OAAO,CAAC,cAAc,CAER;IACd,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,YAAY,CAA+B;;IAcnD,OAAO,CAAC,mBAAmB;IAmB3B,OAAO,CAAC,sBAAsB;YAqChB,aAAa;IAuC3B,OAAO,CAAC,qBAAqB;IAO7B,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,gBAAgB;IAYxB,OAAO,CAAC,qBAAqB;IAa7B,OAAO,CAAC,sBAAsB;IAkC9B,OAAO,CAAC,mBAAmB;YASb,oBAAoB;YAwBpB,oBAAoB;IAwBlC,OAAO,CAAC,iBAAiB;IAsBzB,OAAO,CAAC,aAAa;IA2BrB,OAAO,CAAC,mBAAmB;IAoDrB,aAAa,CACjB,GAAG,EAAE,OAAO,CAAC,OAAO,EACpB,GAAG,EAAE,OAAO,CAAC,QAAQ,EACrB,eAAe,CAAC,EAAE,eAAe,GAChC,OAAO,CAAC,IAAI,CAAC;YAoRF,eAAe;IA4D7B,OAAO,CAAC,SAAS;IAYjB,OAAO,CAAC,gBAAgB;IASlB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAgnBtB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IA2D/B,cAAc,IAAI;QAChB,MAAM,EAAE,OAAO,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,QAAQ,CAAC,EAAE;YACT,KAAK,EAAE,MAAM,CAAC;YACd,MAAM,EAAE,MAAM,CAAC;YACf,OAAO,EAAE,MAAM,CAAC;YAChB,GAAG,EAAE,MAAM,CAAC;YACZ,UAAU,EAAE,MAAM,EAAE,CAAC;SACtB,CAAC;KACH;IAmDM,kBAAkB,IAAI,YAAY,EAAE;IAoEpC,mBAAmB,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,MAAM;CAsG7D"}
|
||||
31
dist/http-server-single-session.js
vendored
31
dist/http-server-single-session.js
vendored
@@ -133,6 +133,15 @@ class SingleSessionHTTPServer {
|
||||
isValidSessionId(sessionId) {
|
||||
return Boolean(sessionId && sessionId.length > 0);
|
||||
}
|
||||
isJsonRpcNotification(body) {
|
||||
if (!body || typeof body !== 'object')
|
||||
return false;
|
||||
const isSingleNotification = (msg) => msg && typeof msg.method === 'string' && !('id' in msg);
|
||||
if (Array.isArray(body)) {
|
||||
return body.length > 0 && body.every(isSingleNotification);
|
||||
}
|
||||
return isSingleNotification(body);
|
||||
}
|
||||
sanitizeErrorForClient(error) {
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
if (error instanceof Error) {
|
||||
@@ -381,6 +390,20 @@ class SingleSessionHTTPServer {
|
||||
}
|
||||
logger_1.logger.info('handleRequest: Reusing existing transport for session', { sessionId });
|
||||
transport = this.transports[sessionId];
|
||||
if (!transport) {
|
||||
if (this.isJsonRpcNotification(req.body)) {
|
||||
logger_1.logger.info('handleRequest: Session removed during lookup, accepting notification', { sessionId });
|
||||
res.status(202).end();
|
||||
return;
|
||||
}
|
||||
logger_1.logger.warn('handleRequest: Session removed between check and use (TOCTOU)', { sessionId });
|
||||
res.status(400).json({
|
||||
jsonrpc: '2.0',
|
||||
error: { code: -32000, message: 'Bad Request: Session not found or expired' },
|
||||
id: req.body?.id || null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const isMultiTenantEnabled = process.env.ENABLE_MULTI_TENANT === 'true';
|
||||
const sessionStrategy = process.env.MULTI_TENANT_SESSION_STRATEGY || 'instance';
|
||||
if (isMultiTenantEnabled && sessionStrategy === 'shared' && instanceContext) {
|
||||
@@ -389,6 +412,14 @@ class SingleSessionHTTPServer {
|
||||
this.updateSessionAccess(sessionId);
|
||||
}
|
||||
else {
|
||||
if (this.isJsonRpcNotification(req.body)) {
|
||||
logger_1.logger.info('handleRequest: Accepting notification for stale/missing session', {
|
||||
method: req.body?.method,
|
||||
sessionId: sessionId || 'none',
|
||||
});
|
||||
res.status(202).end();
|
||||
return;
|
||||
}
|
||||
const errorDetails = {
|
||||
hasSessionId: !!sessionId,
|
||||
isInitialize: isInitialize,
|
||||
|
||||
2
dist/http-server-single-session.js.map
vendored
2
dist/http-server-single-session.js.map
vendored
File diff suppressed because one or more lines are too long
10
dist/mcp/handlers-n8n-manager.d.ts
vendored
10
dist/mcp/handlers-n8n-manager.d.ts
vendored
@@ -26,4 +26,14 @@ export declare function handleDiagnostic(request: any, context?: InstanceContext
|
||||
export declare function handleWorkflowVersions(args: unknown, repository: NodeRepository, context?: InstanceContext): Promise<McpToolResponse>;
|
||||
export declare function handleDeployTemplate(args: unknown, templateService: TemplateService, repository: NodeRepository, context?: InstanceContext): Promise<McpToolResponse>;
|
||||
export declare function handleTriggerWebhookWorkflow(args: unknown, context?: InstanceContext): Promise<McpToolResponse>;
|
||||
export declare function handleCreateTable(args: unknown, context?: InstanceContext): Promise<McpToolResponse>;
|
||||
export declare function handleListTables(args: unknown, context?: InstanceContext): Promise<McpToolResponse>;
|
||||
export declare function handleGetTable(args: unknown, context?: InstanceContext): Promise<McpToolResponse>;
|
||||
export declare function handleUpdateTable(args: unknown, context?: InstanceContext): Promise<McpToolResponse>;
|
||||
export declare function handleDeleteTable(args: unknown, context?: InstanceContext): Promise<McpToolResponse>;
|
||||
export declare function handleGetRows(args: unknown, context?: InstanceContext): Promise<McpToolResponse>;
|
||||
export declare function handleInsertRows(args: unknown, context?: InstanceContext): Promise<McpToolResponse>;
|
||||
export declare function handleUpdateRows(args: unknown, context?: InstanceContext): Promise<McpToolResponse>;
|
||||
export declare function handleUpsertRows(args: unknown, context?: InstanceContext): Promise<McpToolResponse>;
|
||||
export declare function handleDeleteRows(args: unknown, context?: InstanceContext): Promise<McpToolResponse>;
|
||||
//# sourceMappingURL=handlers-n8n-manager.d.ts.map
|
||||
2
dist/mcp/handlers-n8n-manager.d.ts.map
vendored
2
dist/mcp/handlers-n8n-manager.d.ts.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"handlers-n8n-manager.d.ts","sourceRoot":"","sources":["../../src/mcp/handlers-n8n-manager.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"handlers-n8n-manager.d.ts","sourceRoot":"","sources":["../../src/mcp/handlers-n8n-manager.ts"],"names":[],"mappings":"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"}
|
||||
253
dist/mcp/handlers-n8n-manager.js
vendored
253
dist/mcp/handlers-n8n-manager.js
vendored
@@ -56,6 +56,16 @@ exports.handleDiagnostic = handleDiagnostic;
|
||||
exports.handleWorkflowVersions = handleWorkflowVersions;
|
||||
exports.handleDeployTemplate = handleDeployTemplate;
|
||||
exports.handleTriggerWebhookWorkflow = handleTriggerWebhookWorkflow;
|
||||
exports.handleCreateTable = handleCreateTable;
|
||||
exports.handleListTables = handleListTables;
|
||||
exports.handleGetTable = handleGetTable;
|
||||
exports.handleUpdateTable = handleUpdateTable;
|
||||
exports.handleDeleteTable = handleDeleteTable;
|
||||
exports.handleGetRows = handleGetRows;
|
||||
exports.handleInsertRows = handleInsertRows;
|
||||
exports.handleUpdateRows = handleUpdateRows;
|
||||
exports.handleUpsertRows = handleUpsertRows;
|
||||
exports.handleDeleteRows = handleDeleteRows;
|
||||
const n8n_api_client_1 = require("../services/n8n-api-client");
|
||||
const n8n_api_1 = require("../config/n8n-api");
|
||||
const n8n_api_2 = require("../types/n8n-api");
|
||||
@@ -175,6 +185,7 @@ const createWorkflowSchema = zod_1.z.object({
|
||||
executionTimeout: zod_1.z.number().optional(),
|
||||
errorWorkflow: zod_1.z.string().optional(),
|
||||
}).optional(),
|
||||
projectId: zod_1.z.string().optional(),
|
||||
});
|
||||
const updateWorkflowSchema = zod_1.z.object({
|
||||
id: zod_1.z.string(),
|
||||
@@ -1470,7 +1481,7 @@ async function handleDiagnostic(request, context) {
|
||||
}
|
||||
}
|
||||
const documentationTools = 7;
|
||||
const managementTools = apiConfigured ? 13 : 0;
|
||||
const managementTools = apiConfigured ? 14 : 0;
|
||||
const totalTools = documentationTools + managementTools;
|
||||
const versionCheck = await (0, npm_version_checker_1.checkNpmVersion)();
|
||||
const cacheMetricsData = getInstanceCacheMetrics();
|
||||
@@ -2038,4 +2049,244 @@ async function handleTriggerWebhookWorkflow(args, context) {
|
||||
};
|
||||
}
|
||||
}
|
||||
const dataTableFilterConditionSchema = zod_1.z.object({
|
||||
columnName: zod_1.z.string().min(1),
|
||||
condition: zod_1.z.enum(['eq', 'neq', 'like', 'ilike', 'gt', 'gte', 'lt', 'lte']),
|
||||
value: zod_1.z.any(),
|
||||
});
|
||||
const dataTableFilterSchema = zod_1.z.object({
|
||||
type: zod_1.z.enum(['and', 'or']).optional().default('and'),
|
||||
filters: zod_1.z.array(dataTableFilterConditionSchema).min(1, 'At least one filter condition is required'),
|
||||
});
|
||||
const tableIdSchema = zod_1.z.object({
|
||||
tableId: zod_1.z.string().min(1, 'tableId is required'),
|
||||
});
|
||||
const createTableSchema = zod_1.z.object({
|
||||
name: zod_1.z.string().min(1, 'Table name cannot be empty'),
|
||||
columns: zod_1.z.array(zod_1.z.object({
|
||||
name: zod_1.z.string().min(1, 'Column name cannot be empty'),
|
||||
type: zod_1.z.enum(['string', 'number', 'boolean', 'date']).optional(),
|
||||
})).optional(),
|
||||
});
|
||||
const listTablesSchema = zod_1.z.object({
|
||||
limit: zod_1.z.number().min(1).max(100).optional(),
|
||||
cursor: zod_1.z.string().optional(),
|
||||
});
|
||||
const updateTableSchema = tableIdSchema.extend({
|
||||
name: zod_1.z.string().min(1, 'New table name cannot be empty'),
|
||||
});
|
||||
function tryParseJson(val) {
|
||||
if (typeof val !== 'string')
|
||||
return val;
|
||||
try {
|
||||
return JSON.parse(val);
|
||||
}
|
||||
catch {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
const coerceJsonArray = zod_1.z.preprocess(tryParseJson, zod_1.z.array(zod_1.z.record(zod_1.z.unknown())));
|
||||
const coerceJsonObject = zod_1.z.preprocess(tryParseJson, zod_1.z.record(zod_1.z.unknown()));
|
||||
const coerceJsonFilter = zod_1.z.preprocess(tryParseJson, dataTableFilterSchema);
|
||||
const getRowsSchema = tableIdSchema.extend({
|
||||
limit: zod_1.z.number().min(1).max(100).optional(),
|
||||
cursor: zod_1.z.string().optional(),
|
||||
filter: zod_1.z.union([coerceJsonFilter, zod_1.z.string()]).optional(),
|
||||
sortBy: zod_1.z.string().optional(),
|
||||
search: zod_1.z.string().optional(),
|
||||
});
|
||||
const insertRowsSchema = tableIdSchema.extend({
|
||||
data: coerceJsonArray.pipe(zod_1.z.array(zod_1.z.record(zod_1.z.unknown())).min(1, 'At least one row is required')),
|
||||
returnType: zod_1.z.enum(['count', 'id', 'all']).optional(),
|
||||
});
|
||||
const mutateRowsSchema = tableIdSchema.extend({
|
||||
filter: coerceJsonFilter,
|
||||
data: coerceJsonObject,
|
||||
returnData: zod_1.z.boolean().optional(),
|
||||
dryRun: zod_1.z.boolean().optional(),
|
||||
});
|
||||
const deleteRowsSchema = tableIdSchema.extend({
|
||||
filter: coerceJsonFilter,
|
||||
returnData: zod_1.z.boolean().optional(),
|
||||
dryRun: zod_1.z.boolean().optional(),
|
||||
});
|
||||
function handleDataTableError(error) {
|
||||
if (error instanceof zod_1.z.ZodError) {
|
||||
return { success: false, error: 'Invalid input', details: { errors: error.errors } };
|
||||
}
|
||||
if (error instanceof n8n_errors_1.N8nApiError) {
|
||||
return {
|
||||
success: false,
|
||||
error: (0, n8n_errors_1.getUserFriendlyErrorMessage)(error),
|
||||
code: error.code,
|
||||
details: error.details,
|
||||
};
|
||||
}
|
||||
return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred' };
|
||||
}
|
||||
async function handleCreateTable(args, context) {
|
||||
try {
|
||||
const client = ensureApiConfigured(context);
|
||||
const input = createTableSchema.parse(args);
|
||||
const dataTable = await client.createDataTable(input);
|
||||
if (!dataTable || !dataTable.id) {
|
||||
return { success: false, error: 'Data table creation failed: n8n API returned an empty or invalid response' };
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
data: { id: dataTable.id, name: dataTable.name },
|
||||
message: `Data table "${dataTable.name}" created with ID: ${dataTable.id}`,
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
return handleDataTableError(error);
|
||||
}
|
||||
}
|
||||
async function handleListTables(args, context) {
|
||||
try {
|
||||
const client = ensureApiConfigured(context);
|
||||
const input = listTablesSchema.parse(args || {});
|
||||
const result = await client.listDataTables(input);
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
tables: result.data,
|
||||
count: result.data.length,
|
||||
nextCursor: result.nextCursor || undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
return handleDataTableError(error);
|
||||
}
|
||||
}
|
||||
async function handleGetTable(args, context) {
|
||||
try {
|
||||
const client = ensureApiConfigured(context);
|
||||
const { tableId } = tableIdSchema.parse(args);
|
||||
const dataTable = await client.getDataTable(tableId);
|
||||
return { success: true, data: dataTable };
|
||||
}
|
||||
catch (error) {
|
||||
return handleDataTableError(error);
|
||||
}
|
||||
}
|
||||
async function handleUpdateTable(args, context) {
|
||||
try {
|
||||
const client = ensureApiConfigured(context);
|
||||
const { tableId, name } = updateTableSchema.parse(args);
|
||||
const dataTable = await client.updateDataTable(tableId, { name });
|
||||
const rawArgs = args;
|
||||
const hasColumns = rawArgs && typeof rawArgs === 'object' && 'columns' in rawArgs;
|
||||
return {
|
||||
success: true,
|
||||
data: dataTable,
|
||||
message: `Data table renamed to "${dataTable.name}"` +
|
||||
(hasColumns ? '. Note: columns parameter was ignored — table schema is immutable after creation via the public API' : ''),
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
return handleDataTableError(error);
|
||||
}
|
||||
}
|
||||
async function handleDeleteTable(args, context) {
|
||||
try {
|
||||
const client = ensureApiConfigured(context);
|
||||
const { tableId } = tableIdSchema.parse(args);
|
||||
await client.deleteDataTable(tableId);
|
||||
return { success: true, message: `Data table ${tableId} deleted successfully` };
|
||||
}
|
||||
catch (error) {
|
||||
return handleDataTableError(error);
|
||||
}
|
||||
}
|
||||
async function handleGetRows(args, context) {
|
||||
try {
|
||||
const client = ensureApiConfigured(context);
|
||||
const { tableId, filter, sortBy, ...params } = getRowsSchema.parse(args);
|
||||
const queryParams = { ...params };
|
||||
if (filter) {
|
||||
queryParams.filter = typeof filter === 'string' ? filter : JSON.stringify(filter);
|
||||
}
|
||||
if (sortBy) {
|
||||
queryParams.sortBy = sortBy;
|
||||
}
|
||||
const result = await client.getDataTableRows(tableId, queryParams);
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
rows: result.data,
|
||||
count: result.data.length,
|
||||
nextCursor: result.nextCursor || undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
return handleDataTableError(error);
|
||||
}
|
||||
}
|
||||
async function handleInsertRows(args, context) {
|
||||
try {
|
||||
const client = ensureApiConfigured(context);
|
||||
const { tableId, ...params } = insertRowsSchema.parse(args);
|
||||
const result = await client.insertDataTableRows(tableId, params);
|
||||
return {
|
||||
success: true,
|
||||
data: result,
|
||||
message: `Rows inserted into data table ${tableId}`,
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
return handleDataTableError(error);
|
||||
}
|
||||
}
|
||||
async function handleUpdateRows(args, context) {
|
||||
try {
|
||||
const client = ensureApiConfigured(context);
|
||||
const { tableId, ...params } = mutateRowsSchema.parse(args);
|
||||
const result = await client.updateDataTableRows(tableId, params);
|
||||
return {
|
||||
success: true,
|
||||
data: result,
|
||||
message: params.dryRun ? 'Dry run: rows matched (no changes applied)' : 'Rows updated successfully',
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
return handleDataTableError(error);
|
||||
}
|
||||
}
|
||||
async function handleUpsertRows(args, context) {
|
||||
try {
|
||||
const client = ensureApiConfigured(context);
|
||||
const { tableId, ...params } = mutateRowsSchema.parse(args);
|
||||
const result = await client.upsertDataTableRow(tableId, params);
|
||||
return {
|
||||
success: true,
|
||||
data: result,
|
||||
message: params.dryRun ? 'Dry run: upsert previewed (no changes applied)' : 'Row upserted successfully',
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
return handleDataTableError(error);
|
||||
}
|
||||
}
|
||||
async function handleDeleteRows(args, context) {
|
||||
try {
|
||||
const client = ensureApiConfigured(context);
|
||||
const { tableId, filter, ...params } = deleteRowsSchema.parse(args);
|
||||
const queryParams = {
|
||||
filter: JSON.stringify(filter),
|
||||
...params,
|
||||
};
|
||||
const result = await client.deleteDataTableRows(tableId, queryParams);
|
||||
return {
|
||||
success: true,
|
||||
data: result,
|
||||
message: params.dryRun ? 'Dry run: rows matched for deletion (no changes applied)' : 'Rows deleted successfully',
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
return handleDataTableError(error);
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=handlers-n8n-manager.js.map
|
||||
2
dist/mcp/handlers-n8n-manager.js.map
vendored
2
dist/mcp/handlers-n8n-manager.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/mcp/handlers-workflow-diff.d.ts.map
vendored
2
dist/mcp/handlers-workflow-diff.d.ts.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"handlers-workflow-diff.d.ts","sourceRoot":"","sources":["../../src/mcp/handlers-workflow-diff.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAMnD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAgF7D,wBAAsB,2BAA2B,CAC/C,IAAI,EAAE,OAAO,EACb,UAAU,EAAE,cAAc,EAC1B,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,eAAe,CAAC,CA6Z1B"}
|
||||
{"version":3,"file":"handlers-workflow-diff.d.ts","sourceRoot":"","sources":["../../src/mcp/handlers-workflow-diff.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAMnD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAkF7D,wBAAsB,2BAA2B,CAC/C,IAAI,EAAE,OAAO,EACb,UAAU,EAAE,cAAc,EAC1B,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,eAAe,CAAC,CAib1B"}
|
||||
24
dist/mcp/handlers-workflow-diff.js
vendored
24
dist/mcp/handlers-workflow-diff.js
vendored
@@ -79,6 +79,7 @@ const workflowDiffSchema = zod_1.z.object({
|
||||
settings: zod_1.z.any().optional(),
|
||||
name: zod_1.z.string().optional(),
|
||||
tag: zod_1.z.string().optional(),
|
||||
destinationProjectId: zod_1.z.string().min(1).optional(),
|
||||
id: zod_1.z.string().optional(),
|
||||
}).transform((op) => {
|
||||
if (NODE_TARGETING_OPERATIONS.has(op.type)) {
|
||||
@@ -329,6 +330,25 @@ async function handleUpdatePartialWorkflow(args, repository, context) {
|
||||
logger_1.logger.warn('Tag operations failed (non-blocking)', tagError);
|
||||
}
|
||||
}
|
||||
let transferMessage = '';
|
||||
if (diffResult.transferToProjectId) {
|
||||
try {
|
||||
await client.transferWorkflow(input.id, diffResult.transferToProjectId);
|
||||
transferMessage = ` Workflow transferred to project ${diffResult.transferToProjectId}.`;
|
||||
}
|
||||
catch (transferError) {
|
||||
logger_1.logger.error('Failed to transfer workflow to project', transferError);
|
||||
return {
|
||||
success: false,
|
||||
saved: true,
|
||||
error: 'Workflow updated successfully but project transfer failed',
|
||||
details: {
|
||||
workflowUpdated: true,
|
||||
transferError: transferError instanceof Error ? transferError.message : 'Unknown error'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
let finalWorkflow = updatedWorkflow;
|
||||
let activationMessage = '';
|
||||
try {
|
||||
@@ -409,7 +429,7 @@ async function handleUpdatePartialWorkflow(args, repository, context) {
|
||||
nodeCount: finalWorkflow.nodes?.length || 0,
|
||||
operationsApplied: diffResult.operationsApplied
|
||||
},
|
||||
message: `Workflow "${finalWorkflow.name}" updated successfully. Applied ${diffResult.operationsApplied} operations.${activationMessage} Use n8n_get_workflow with mode 'structure' to verify current state.`,
|
||||
message: `Workflow "${finalWorkflow.name}" updated successfully. Applied ${diffResult.operationsApplied} operations.${transferMessage}${activationMessage} Use n8n_get_workflow with mode 'structure' to verify current state.`,
|
||||
details: {
|
||||
applied: diffResult.applied,
|
||||
failed: diffResult.failed,
|
||||
@@ -498,6 +518,8 @@ function inferIntentFromOperations(operations) {
|
||||
return 'Activate workflow';
|
||||
case 'deactivateWorkflow':
|
||||
return 'Deactivate workflow';
|
||||
case 'transferWorkflow':
|
||||
return `Transfer workflow to project ${op.destinationProjectId || ''}`.trim();
|
||||
default:
|
||||
return `Workflow ${op.type}`;
|
||||
}
|
||||
|
||||
2
dist/mcp/handlers-workflow-diff.js.map
vendored
2
dist/mcp/handlers-workflow-diff.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/mcp/server.d.ts.map
vendored
2
dist/mcp/server.d.ts.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AA0CA,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAmGnE,qBAAa,yBAAyB;IACpC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,EAAE,CAAgC;IAC1C,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,eAAe,CAAgC;IACvD,OAAO,CAAC,WAAW,CAAgB;IACnC,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,eAAe,CAAC,CAAkB;IAC1C,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,qBAAqB,CAAsB;IACnD,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,kBAAkB,CAA4B;IACtD,OAAO,CAAC,iBAAiB,CAAkB;IAC3C,OAAO,CAAC,aAAa,CAAoC;IACzD,OAAO,CAAC,UAAU,CAAkB;gBAExB,eAAe,CAAC,EAAE,eAAe,EAAE,WAAW,CAAC,EAAE,gBAAgB;IA8GvE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YA+Cd,kBAAkB;YAiDlB,wBAAwB;IA0BtC,OAAO,CAAC,kBAAkB;YA6CZ,iBAAiB;IAa/B,OAAO,CAAC,eAAe,CAAkB;YAE3B,sBAAsB;IAgDpC,OAAO,CAAC,gBAAgB;IAqCxB,OAAO,CAAC,aAAa;IA0XrB,OAAO,CAAC,wBAAwB;IAoFhC,OAAO,CAAC,kBAAkB;IAqE1B,OAAO,CAAC,uBAAuB;IAwB/B,OAAO,CAAC,qBAAqB;IAiF7B,OAAO,CAAC,2BAA2B;YA0UrB,SAAS;YA2DT,WAAW;YAkFX,WAAW;YA0CX,cAAc;YA8Md,gBAAgB;IAqD9B,OAAO,CAAC,mBAAmB;IAwE3B,OAAO,CAAC,eAAe;YAsBT,eAAe;IA2L7B,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,uBAAuB;IA0D/B,OAAO,CAAC,iBAAiB;YAqFX,WAAW;YAgCX,oBAAoB;IAuFlC,OAAO,CAAC,aAAa;YAQP,qBAAqB;YAwDrB,iBAAiB;YAiKjB,OAAO;YAgDP,cAAc;YAwFd,iBAAiB;IAqC/B,OAAO,CAAC,iBAAiB;IA0BzB,OAAO,CAAC,iBAAiB;IA0BzB,OAAO,CAAC,eAAe;IAwCvB,OAAO,CAAC,kBAAkB;IAiC1B,OAAO,CAAC,aAAa;IAoCrB,OAAO,CAAC,0BAA0B;IAgClC,OAAO,CAAC,4BAA4B;YAKtB,oBAAoB;IAsDlC,OAAO,CAAC,gBAAgB;YAiBV,SAAS;YA6CT,kBAAkB;YAqElB,uBAAuB;YAsDvB,iBAAiB;IAqE/B,OAAO,CAAC,qBAAqB;IA8C7B,OAAO,CAAC,uBAAuB;IA4D/B,OAAO,CAAC,wBAAwB;IAkChC,OAAO,CAAC,iBAAiB;YAoDX,mBAAmB;YAoEnB,qBAAqB;IAS7B,OAAO,CAAC,SAAS,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;YAS9B,aAAa;YAcb,iBAAiB;YAoBjB,WAAW;YAwBX,eAAe;YAqBf,mBAAmB;YAwBnB,yBAAyB;IA4CvC,OAAO,CAAC,kBAAkB;YAiBZ,gBAAgB;YA6HhB,2BAA2B;YAiE3B,2BAA2B;IAyEnC,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IA0BpB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAgEhC"}
|
||||
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AA0CA,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAmGnE,qBAAa,yBAAyB;IACpC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,EAAE,CAAgC;IAC1C,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,eAAe,CAAgC;IACvD,OAAO,CAAC,WAAW,CAAgB;IACnC,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,eAAe,CAAC,CAAkB;IAC1C,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,qBAAqB,CAAsB;IACnD,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,kBAAkB,CAA4B;IACtD,OAAO,CAAC,iBAAiB,CAAkB;IAC3C,OAAO,CAAC,aAAa,CAAoC;IACzD,OAAO,CAAC,UAAU,CAAkB;gBAExB,eAAe,CAAC,EAAE,eAAe,EAAE,WAAW,CAAC,EAAE,gBAAgB;IA8GvE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YA+Cd,kBAAkB;YAiDlB,wBAAwB;IA0BtC,OAAO,CAAC,kBAAkB;YA6CZ,iBAAiB;IAa/B,OAAO,CAAC,eAAe,CAAkB;YAE3B,sBAAsB;IAgDpC,OAAO,CAAC,gBAAgB;IAqCxB,OAAO,CAAC,aAAa;IA0XrB,OAAO,CAAC,wBAAwB;IAoFhC,OAAO,CAAC,kBAAkB;IA0E1B,OAAO,CAAC,uBAAuB;IAwB/B,OAAO,CAAC,qBAAqB;IAiF7B,OAAO,CAAC,2BAA2B;YA8VrB,SAAS;YA2DT,WAAW;YAkFX,WAAW;YA0CX,cAAc;YA8Md,gBAAgB;IAqD9B,OAAO,CAAC,mBAAmB;IAwE3B,OAAO,CAAC,eAAe;YAsBT,eAAe;IA2L7B,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,uBAAuB;IA0D/B,OAAO,CAAC,iBAAiB;YAqFX,WAAW;YAgCX,oBAAoB;IAuFlC,OAAO,CAAC,aAAa;YAQP,qBAAqB;YAwDrB,iBAAiB;YAiKjB,OAAO;YAgDP,cAAc;YAwFd,iBAAiB;IAqC/B,OAAO,CAAC,iBAAiB;IA0BzB,OAAO,CAAC,iBAAiB;IA0BzB,OAAO,CAAC,eAAe;IAwCvB,OAAO,CAAC,kBAAkB;IAiC1B,OAAO,CAAC,aAAa;IAoCrB,OAAO,CAAC,0BAA0B;IAgClC,OAAO,CAAC,4BAA4B;YAKtB,oBAAoB;IAsDlC,OAAO,CAAC,gBAAgB;YAiBV,SAAS;YA6CT,kBAAkB;YAqElB,uBAAuB;YAsDvB,iBAAiB;IAqE/B,OAAO,CAAC,qBAAqB;IA8C7B,OAAO,CAAC,uBAAuB;IA4D/B,OAAO,CAAC,wBAAwB;IAkChC,OAAO,CAAC,iBAAiB;YAoDX,mBAAmB;YAoEnB,qBAAqB;IAS7B,OAAO,CAAC,SAAS,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;YAS9B,aAAa;YAcb,iBAAiB;YAoBjB,WAAW;YAwBX,eAAe;YAqBf,mBAAmB;YAwBnB,yBAAyB;IA4CvC,OAAO,CAAC,kBAAkB;YAiBZ,gBAAgB;YA6HhB,2BAA2B;YAiE3B,2BAA2B;IAyEnC,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IA0BpB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAgEhC"}
|
||||
23
dist/mcp/server.js
vendored
23
dist/mcp/server.js
vendored
@@ -720,6 +720,11 @@ class N8NDocumentationMCPServer {
|
||||
? { valid: true, errors: [] }
|
||||
: { valid: false, errors: [{ field: 'action', message: 'action is required' }] };
|
||||
break;
|
||||
case 'n8n_manage_datatable':
|
||||
validationResult = args.action
|
||||
? { valid: true, errors: [] }
|
||||
: { valid: false, errors: [{ field: 'action', message: 'action is required' }] };
|
||||
break;
|
||||
case 'n8n_deploy_template':
|
||||
validationResult = args.templateId !== undefined
|
||||
? { valid: true, errors: [] }
|
||||
@@ -1109,6 +1114,24 @@ class N8NDocumentationMCPServer {
|
||||
if (!this.repository)
|
||||
throw new Error('Repository not initialized');
|
||||
return n8nHandlers.handleDeployTemplate(args, this.templateService, this.repository, this.instanceContext);
|
||||
case 'n8n_manage_datatable': {
|
||||
this.validateToolParams(name, args, ['action']);
|
||||
const dtAction = args.action;
|
||||
switch (dtAction) {
|
||||
case 'createTable': return n8nHandlers.handleCreateTable(args, this.instanceContext);
|
||||
case 'listTables': return n8nHandlers.handleListTables(args, this.instanceContext);
|
||||
case 'getTable': return n8nHandlers.handleGetTable(args, this.instanceContext);
|
||||
case 'updateTable': return n8nHandlers.handleUpdateTable(args, this.instanceContext);
|
||||
case 'deleteTable': return n8nHandlers.handleDeleteTable(args, this.instanceContext);
|
||||
case 'getRows': return n8nHandlers.handleGetRows(args, this.instanceContext);
|
||||
case 'insertRows': return n8nHandlers.handleInsertRows(args, this.instanceContext);
|
||||
case 'updateRows': return n8nHandlers.handleUpdateRows(args, this.instanceContext);
|
||||
case 'upsertRows': return n8nHandlers.handleUpsertRows(args, this.instanceContext);
|
||||
case 'deleteRows': return n8nHandlers.handleDeleteRows(args, this.instanceContext);
|
||||
default:
|
||||
throw new Error(`Unknown action: ${dtAction}. Valid actions: createTable, listTables, getTable, updateTable, deleteTable, getRows, insertRows, updateRows, upsertRows, deleteRows`);
|
||||
}
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${name}`);
|
||||
}
|
||||
|
||||
2
dist/mcp/server.js.map
vendored
2
dist/mcp/server.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/mcp/tool-docs/index.d.ts.map
vendored
2
dist/mcp/tool-docs/index.d.ts.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/mcp/tool-docs/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AA4B5C,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAmChE,CAAC;AAGF,YAAY,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC"}
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/mcp/tool-docs/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AA6B5C,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAoChE,CAAC;AAGF,YAAY,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC"}
|
||||
3
dist/mcp/tool-docs/index.js
vendored
3
dist/mcp/tool-docs/index.js
vendored
@@ -29,6 +29,7 @@ exports.toolsDocumentation = {
|
||||
n8n_test_workflow: workflow_management_1.n8nTestWorkflowDoc,
|
||||
n8n_executions: workflow_management_1.n8nExecutionsDoc,
|
||||
n8n_workflow_versions: workflow_management_1.n8nWorkflowVersionsDoc,
|
||||
n8n_deploy_template: workflow_management_1.n8nDeployTemplateDoc
|
||||
n8n_deploy_template: workflow_management_1.n8nDeployTemplateDoc,
|
||||
n8n_manage_datatable: workflow_management_1.n8nManageDatatableDoc
|
||||
};
|
||||
//# sourceMappingURL=index.js.map
|
||||
2
dist/mcp/tool-docs/index.js.map
vendored
2
dist/mcp/tool-docs/index.js.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/mcp/tool-docs/index.ts"],"names":[],"mappings":";;;AAGA,2CAA6C;AAC7C,mDAA6C;AAC7C,6CAAoE;AACpE,2CAAiE;AACjE,qCAGkB;AAClB,qCAAyC;AACzC,+DAa+B;AAGlB,QAAA,kBAAkB,GAAsC;IAEnE,mBAAmB,EAAE,8BAAqB;IAC1C,gBAAgB,EAAE,0BAAiB;IAGnC,eAAe,EAAE,sBAAa;IAG9B,YAAY,EAAE,0BAAc;IAG5B,QAAQ,EAAE,0BAAU;IAGpB,aAAa,EAAE,4BAAe;IAC9B,iBAAiB,EAAE,gCAAmB;IAGtC,YAAY,EAAE,0BAAc;IAC5B,gBAAgB,EAAE,8BAAkB;IAGpC,mBAAmB,EAAE,0CAAoB;IACzC,gBAAgB,EAAE,uCAAiB;IACnC,wBAAwB,EAAE,8CAAwB;IAClD,2BAA2B,EAAE,iDAA2B;IACxD,mBAAmB,EAAE,0CAAoB;IACzC,kBAAkB,EAAE,yCAAmB;IACvC,qBAAqB,EAAE,4CAAsB;IAC7C,oBAAoB,EAAE,2CAAqB;IAC3C,iBAAiB,EAAE,wCAAkB;IACrC,cAAc,EAAE,sCAAgB;IAChC,qBAAqB,EAAE,4CAAsB;IAC7C,mBAAmB,EAAE,0CAAoB;CAC1C,CAAC"}
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/mcp/tool-docs/index.ts"],"names":[],"mappings":";;;AAGA,2CAA6C;AAC7C,mDAA6C;AAC7C,6CAAoE;AACpE,2CAAiE;AACjE,qCAGkB;AAClB,qCAAyC;AACzC,+DAc+B;AAGlB,QAAA,kBAAkB,GAAsC;IAEnE,mBAAmB,EAAE,8BAAqB;IAC1C,gBAAgB,EAAE,0BAAiB;IAGnC,eAAe,EAAE,sBAAa;IAG9B,YAAY,EAAE,0BAAc;IAG5B,QAAQ,EAAE,0BAAU;IAGpB,aAAa,EAAE,4BAAe;IAC9B,iBAAiB,EAAE,gCAAmB;IAGtC,YAAY,EAAE,0BAAc;IAC5B,gBAAgB,EAAE,8BAAkB;IAGpC,mBAAmB,EAAE,0CAAoB;IACzC,gBAAgB,EAAE,uCAAiB;IACnC,wBAAwB,EAAE,8CAAwB;IAClD,2BAA2B,EAAE,iDAA2B;IACxD,mBAAmB,EAAE,0CAAoB;IACzC,kBAAkB,EAAE,yCAAmB;IACvC,qBAAqB,EAAE,4CAAsB;IAC7C,oBAAoB,EAAE,2CAAqB;IAC3C,iBAAiB,EAAE,wCAAkB;IACrC,cAAc,EAAE,sCAAgB;IAChC,qBAAqB,EAAE,4CAAsB;IAC7C,mBAAmB,EAAE,0CAAoB;IACzC,oBAAoB,EAAE,2CAAqB;CAC5C,CAAC"}
|
||||
@@ -10,4 +10,5 @@ export { n8nTestWorkflowDoc } from './n8n-test-workflow';
|
||||
export { n8nExecutionsDoc } from './n8n-executions';
|
||||
export { n8nWorkflowVersionsDoc } from './n8n-workflow-versions';
|
||||
export { n8nDeployTemplateDoc } from './n8n-deploy-template';
|
||||
export { n8nManageDatatableDoc } from './n8n-manage-datatable';
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/mcp/tool-docs/workflow_management/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC"}
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/mcp/tool-docs/workflow_management/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC"}
|
||||
@@ -1,6 +1,6 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.n8nDeployTemplateDoc = exports.n8nWorkflowVersionsDoc = exports.n8nExecutionsDoc = exports.n8nTestWorkflowDoc = exports.n8nAutofixWorkflowDoc = exports.n8nValidateWorkflowDoc = exports.n8nListWorkflowsDoc = exports.n8nDeleteWorkflowDoc = exports.n8nUpdatePartialWorkflowDoc = exports.n8nUpdateFullWorkflowDoc = exports.n8nGetWorkflowDoc = exports.n8nCreateWorkflowDoc = void 0;
|
||||
exports.n8nManageDatatableDoc = exports.n8nDeployTemplateDoc = exports.n8nWorkflowVersionsDoc = exports.n8nExecutionsDoc = exports.n8nTestWorkflowDoc = exports.n8nAutofixWorkflowDoc = exports.n8nValidateWorkflowDoc = exports.n8nListWorkflowsDoc = exports.n8nDeleteWorkflowDoc = exports.n8nUpdatePartialWorkflowDoc = exports.n8nUpdateFullWorkflowDoc = exports.n8nGetWorkflowDoc = exports.n8nCreateWorkflowDoc = void 0;
|
||||
var n8n_create_workflow_1 = require("./n8n-create-workflow");
|
||||
Object.defineProperty(exports, "n8nCreateWorkflowDoc", { enumerable: true, get: function () { return n8n_create_workflow_1.n8nCreateWorkflowDoc; } });
|
||||
var n8n_get_workflow_1 = require("./n8n-get-workflow");
|
||||
@@ -25,4 +25,6 @@ var n8n_workflow_versions_1 = require("./n8n-workflow-versions");
|
||||
Object.defineProperty(exports, "n8nWorkflowVersionsDoc", { enumerable: true, get: function () { return n8n_workflow_versions_1.n8nWorkflowVersionsDoc; } });
|
||||
var n8n_deploy_template_1 = require("./n8n-deploy-template");
|
||||
Object.defineProperty(exports, "n8nDeployTemplateDoc", { enumerable: true, get: function () { return n8n_deploy_template_1.n8nDeployTemplateDoc; } });
|
||||
var n8n_manage_datatable_1 = require("./n8n-manage-datatable");
|
||||
Object.defineProperty(exports, "n8nManageDatatableDoc", { enumerable: true, get: function () { return n8n_manage_datatable_1.n8nManageDatatableDoc; } });
|
||||
//# sourceMappingURL=index.js.map
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/mcp/tool-docs/workflow_management/index.ts"],"names":[],"mappings":";;;AAAA,6DAA6D;AAApD,2HAAA,oBAAoB,OAAA;AAC7B,uDAAuD;AAA9C,qHAAA,iBAAiB,OAAA;AAC1B,uEAAsE;AAA7D,oIAAA,wBAAwB,OAAA;AACjC,6EAA4E;AAAnE,0IAAA,2BAA2B,OAAA;AACpC,6DAA6D;AAApD,2HAAA,oBAAoB,OAAA;AAC7B,2DAA2D;AAAlD,yHAAA,mBAAmB,OAAA;AAC5B,iEAAiE;AAAxD,+HAAA,sBAAsB,OAAA;AAC/B,+DAA+D;AAAtD,6HAAA,qBAAqB,OAAA;AAC9B,yDAAyD;AAAhD,uHAAA,kBAAkB,OAAA;AAC3B,mDAAoD;AAA3C,kHAAA,gBAAgB,OAAA;AACzB,iEAAiE;AAAxD,+HAAA,sBAAsB,OAAA;AAC/B,6DAA6D;AAApD,2HAAA,oBAAoB,OAAA"}
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/mcp/tool-docs/workflow_management/index.ts"],"names":[],"mappings":";;;AAAA,6DAA6D;AAApD,2HAAA,oBAAoB,OAAA;AAC7B,uDAAuD;AAA9C,qHAAA,iBAAiB,OAAA;AAC1B,uEAAsE;AAA7D,oIAAA,wBAAwB,OAAA;AACjC,6EAA4E;AAAnE,0IAAA,2BAA2B,OAAA;AACpC,6DAA6D;AAApD,2HAAA,oBAAoB,OAAA;AAC7B,2DAA2D;AAAlD,yHAAA,mBAAmB,OAAA;AAC5B,iEAAiE;AAAxD,+HAAA,sBAAsB,OAAA;AAC/B,+DAA+D;AAAtD,6HAAA,qBAAqB,OAAA;AAC9B,yDAAyD;AAAhD,uHAAA,kBAAkB,OAAA;AAC3B,mDAAoD;AAA3C,kHAAA,gBAAgB,OAAA;AACzB,iEAAiE;AAAxD,+HAAA,sBAAsB,OAAA;AAC/B,6DAA6D;AAApD,2HAAA,oBAAoB,OAAA;AAC7B,+DAA+D;AAAtD,6HAAA,qBAAqB,OAAA"}
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"n8n-update-partial-workflow.d.ts","sourceRoot":"","sources":["../../../../src/mcp/tool-docs/workflow_management/n8n-update-partial-workflow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAE7C,eAAO,MAAM,2BAA2B,EAAE,iBA+ZzC,CAAC"}
|
||||
{"version":3,"file":"n8n-update-partial-workflow.d.ts","sourceRoot":"","sources":["../../../../src/mcp/tool-docs/workflow_management/n8n-update-partial-workflow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAE7C,eAAO,MAAM,2BAA2B,EAAE,iBAuazC,CAAC"}
|
||||
@@ -5,7 +5,7 @@ exports.n8nUpdatePartialWorkflowDoc = {
|
||||
name: 'n8n_update_partial_workflow',
|
||||
category: 'workflow_management',
|
||||
essentials: {
|
||||
description: 'Update workflow incrementally with diff operations. Types: addNode, removeNode, updateNode, moveNode, enable/disableNode, addConnection, removeConnection, rewireConnection, cleanStaleConnections, replaceConnections, updateSettings, updateName, add/removeTag, activateWorkflow, deactivateWorkflow. Supports smart parameters (branch, case) for multi-output nodes. Full support for AI connections (ai_languageModel, ai_tool, ai_memory, ai_embedding, ai_vectorStore, ai_document, ai_textSplitter, ai_outputParser).',
|
||||
description: 'Update workflow incrementally with diff operations. Types: addNode, removeNode, updateNode, moveNode, enable/disableNode, addConnection, removeConnection, rewireConnection, cleanStaleConnections, replaceConnections, updateSettings, updateName, add/removeTag, activateWorkflow, deactivateWorkflow, transferWorkflow. Supports smart parameters (branch, case) for multi-output nodes. Full support for AI connections (ai_languageModel, ai_tool, ai_memory, ai_embedding, ai_vectorStore, ai_document, ai_textSplitter, ai_outputParser).',
|
||||
keyParameters: ['id', 'operations', 'continueOnError'],
|
||||
example: 'n8n_update_partial_workflow({id: "wf_123", operations: [{type: "rewireConnection", source: "IF", from: "Old", to: "New", branch: "true"}]})',
|
||||
performance: 'Fast (50-200ms)',
|
||||
@@ -23,7 +23,8 @@ exports.n8nUpdatePartialWorkflowDoc = {
|
||||
'Batch AI component connections for atomic updates',
|
||||
'Auto-sanitization: ALL nodes auto-fixed during updates (operator structures, missing metadata)',
|
||||
'Node renames automatically update all connection references - no manual connection operations needed',
|
||||
'Activate/deactivate workflows: Use activateWorkflow/deactivateWorkflow operations (requires activatable triggers like webhook/schedule)'
|
||||
'Activate/deactivate workflows: Use activateWorkflow/deactivateWorkflow operations (requires activatable triggers like webhook/schedule)',
|
||||
'Transfer workflows between projects: Use transferWorkflow with destinationProjectId (enterprise feature)'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
@@ -56,6 +57,9 @@ exports.n8nUpdatePartialWorkflowDoc = {
|
||||
- **activateWorkflow**: Activate the workflow to enable automatic execution via triggers
|
||||
- **deactivateWorkflow**: Deactivate the workflow to prevent automatic execution
|
||||
|
||||
### Project Management Operations (1 type):
|
||||
- **transferWorkflow**: Transfer the workflow to a different project. Requires \`destinationProjectId\`. Enterprise/cloud feature.
|
||||
|
||||
## Smart Parameters for Multi-Output Nodes
|
||||
|
||||
For **IF nodes**, use semantic 'branch' parameter instead of technical sourceIndex:
|
||||
@@ -346,7 +350,10 @@ n8n_update_partial_workflow({
|
||||
'// Migrate from deprecated continueOnFail to onError\nn8n_update_partial_workflow({id: "rm2", operations: [{type: "updateNode", nodeName: "HTTP Request", updates: {continueOnFail: null, onError: "continueErrorOutput"}}]})',
|
||||
'// Remove nested property\nn8n_update_partial_workflow({id: "rm3", operations: [{type: "updateNode", nodeName: "API Request", updates: {"parameters.authentication": null}}]})',
|
||||
'// Remove multiple properties\nn8n_update_partial_workflow({id: "rm4", operations: [{type: "updateNode", nodeName: "Data Processor", updates: {continueOnFail: null, alwaysOutputData: null, "parameters.legacy_option": null}}]})',
|
||||
'// Remove entire array property\nn8n_update_partial_workflow({id: "rm5", operations: [{type: "updateNode", nodeName: "HTTP Request", updates: {"parameters.headers": null}}]})'
|
||||
'// Remove entire array property\nn8n_update_partial_workflow({id: "rm5", operations: [{type: "updateNode", nodeName: "HTTP Request", updates: {"parameters.headers": null}}]})',
|
||||
'\n// ============ PROJECT TRANSFER EXAMPLES ============',
|
||||
'// Transfer workflow to a different project\nn8n_update_partial_workflow({id: "tf1", operations: [{type: "transferWorkflow", destinationProjectId: "project-abc-123"}]})',
|
||||
'// Transfer and activate in one call\nn8n_update_partial_workflow({id: "tf2", operations: [{type: "transferWorkflow", destinationProjectId: "project-abc-123"}, {type: "activateWorkflow"}]})'
|
||||
],
|
||||
useCases: [
|
||||
'Rewire connections when replacing nodes',
|
||||
@@ -364,7 +371,8 @@ n8n_update_partial_workflow({
|
||||
'Add fallback language models to AI Agents',
|
||||
'Configure Vector Store retrieval systems',
|
||||
'Swap language models in existing AI workflows',
|
||||
'Batch-update AI tool connections'
|
||||
'Batch-update AI tool connections',
|
||||
'Transfer workflows between team projects (enterprise)'
|
||||
],
|
||||
performance: 'Very fast - typically 50-200ms. Much faster than full updates as only changes are processed.',
|
||||
bestPractices: [
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"n8n-update-partial-workflow.js","sourceRoot":"","sources":["../../../../src/mcp/tool-docs/workflow_management/n8n-update-partial-workflow.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"n8n-update-partial-workflow.js","sourceRoot":"","sources":["../../../../src/mcp/tool-docs/workflow_management/n8n-update-partial-workflow.ts"],"names":[],"mappings":"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"}
|
||||
2
dist/mcp/tools-n8n-manager.d.ts.map
vendored
2
dist/mcp/tools-n8n-manager.d.ts.map
vendored
@@ -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,EAqlB9C,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,EAwoB9C,CAAC"}
|
||||
55
dist/mcp/tools-n8n-manager.js
vendored
55
dist/mcp/tools-n8n-manager.js
vendored
@@ -57,6 +57,10 @@ exports.n8nManagementTools = [
|
||||
executionTimeout: { type: 'number' },
|
||||
errorWorkflow: { type: 'string' }
|
||||
}
|
||||
},
|
||||
projectId: {
|
||||
type: 'string',
|
||||
description: 'Optional project ID to create the workflow in (enterprise feature)'
|
||||
}
|
||||
},
|
||||
required: ['name', 'nodes', 'connections']
|
||||
@@ -137,7 +141,7 @@ exports.n8nManagementTools = [
|
||||
},
|
||||
{
|
||||
name: 'n8n_update_partial_workflow',
|
||||
description: `Update workflow incrementally with diff operations. Types: addNode, removeNode, updateNode, moveNode, enable/disableNode, addConnection, removeConnection, updateSettings, updateName, add/removeTag. See tools_documentation("n8n_update_partial_workflow", "full") for details.`,
|
||||
description: `Update workflow incrementally with diff operations. Types: addNode, removeNode, updateNode, moveNode, enable/disableNode, addConnection, removeConnection, updateSettings, updateName, add/removeTag, activate/deactivateWorkflow, transferWorkflow. See tools_documentation("n8n_update_partial_workflow", "full") for details.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
additionalProperties: true,
|
||||
@@ -583,6 +587,53 @@ exports.n8nManagementTools = [
|
||||
destructiveHint: false,
|
||||
openWorldHint: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'n8n_manage_datatable',
|
||||
description: `Manage n8n data tables and rows. Actions: createTable, listTables, getTable, updateTable, deleteTable, getRows, insertRows, updateRows, upsertRows, deleteRows.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
action: {
|
||||
type: 'string',
|
||||
enum: ['createTable', 'listTables', 'getTable', 'updateTable', 'deleteTable', 'getRows', 'insertRows', 'updateRows', 'upsertRows', 'deleteRows'],
|
||||
description: 'Operation to perform',
|
||||
},
|
||||
tableId: { type: 'string', description: 'Data table ID (required for all actions except createTable and listTables)' },
|
||||
name: { type: 'string', description: 'For createTable: table name. For updateTable: new name (rename only — schema is immutable after creation)' },
|
||||
columns: {
|
||||
type: 'array',
|
||||
description: 'For createTable only: column definitions (schema is immutable after creation via public API)',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
type: { type: 'string', enum: ['string', 'number', 'boolean', 'date'] },
|
||||
},
|
||||
required: ['name'],
|
||||
},
|
||||
},
|
||||
data: { description: 'For insertRows: array of row objects. For updateRows/upsertRows: object with column values.' },
|
||||
filter: {
|
||||
type: 'object',
|
||||
description: 'For getRows/updateRows/upsertRows/deleteRows: {type?: "and"|"or", filters: [{columnName, condition, value}]}',
|
||||
},
|
||||
limit: { type: 'number', description: 'For listTables/getRows: max results (1-100)' },
|
||||
cursor: { type: 'string', description: 'For listTables/getRows: pagination cursor' },
|
||||
sortBy: { type: 'string', description: 'For getRows: "columnName:asc" or "columnName:desc"' },
|
||||
search: { type: 'string', description: 'For getRows: text search across string columns' },
|
||||
returnType: { type: 'string', enum: ['count', 'id', 'all'], description: 'For insertRows: what to return (default: count)' },
|
||||
returnData: { type: 'boolean', description: 'For updateRows/upsertRows/deleteRows: return affected rows (default: false)' },
|
||||
dryRun: { type: 'boolean', description: 'For updateRows/upsertRows/deleteRows: preview without applying (default: false)' },
|
||||
},
|
||||
required: ['action'],
|
||||
},
|
||||
annotations: {
|
||||
title: 'Manage Data Tables',
|
||||
readOnlyHint: false,
|
||||
destructiveHint: true,
|
||||
openWorldHint: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
//# sourceMappingURL=tools-n8n-manager.js.map
|
||||
2
dist/mcp/tools-n8n-manager.js.map
vendored
2
dist/mcp/tools-n8n-manager.js.map
vendored
File diff suppressed because one or more lines are too long
3
dist/scripts/rebuild.js
vendored
3
dist/scripts/rebuild.js
vendored
@@ -130,6 +130,9 @@ async function rebuild() {
|
||||
}
|
||||
}
|
||||
console.log(`💾 Save completed: ${saved} nodes saved successfully`);
|
||||
console.log('\n🔍 Rebuilding FTS5 search index...');
|
||||
db.prepare("INSERT INTO nodes_fts(nodes_fts) VALUES('rebuild')").run();
|
||||
console.log('✅ FTS5 index rebuilt successfully');
|
||||
console.log('\n🔍 Running validation checks...');
|
||||
try {
|
||||
const validationResults = validateDatabase(repository);
|
||||
|
||||
2
dist/scripts/rebuild.js.map
vendored
2
dist/scripts/rebuild.js.map
vendored
File diff suppressed because one or more lines are too long
25
dist/services/n8n-api-client.d.ts
vendored
25
dist/services/n8n-api-client.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
import { Workflow, WorkflowListParams, WorkflowListResponse, Execution, ExecutionListParams, ExecutionListResponse, Credential, CredentialListParams, CredentialListResponse, Tag, TagListParams, TagListResponse, HealthCheckResponse, N8nVersionInfo, Variable, WebhookRequest, SourceControlStatus, SourceControlPullResult, SourceControlPushResult } from '../types/n8n-api';
|
||||
import { Workflow, WorkflowListParams, WorkflowListResponse, Execution, ExecutionListParams, ExecutionListResponse, Credential, CredentialListParams, CredentialListResponse, Tag, TagListParams, TagListResponse, HealthCheckResponse, N8nVersionInfo, Variable, WebhookRequest, SourceControlStatus, SourceControlPullResult, SourceControlPushResult, DataTable, DataTableColumn, DataTableListParams, DataTableRow, DataTableRowListParams, DataTableInsertRowsParams, DataTableUpdateRowsParams, DataTableUpsertRowParams, DataTableDeleteRowsParams } from '../types/n8n-api';
|
||||
export interface N8nApiClientConfig {
|
||||
baseUrl: string;
|
||||
apiKey: string;
|
||||
@@ -20,6 +20,7 @@ export declare class N8nApiClient {
|
||||
getWorkflow(id: string): Promise<Workflow>;
|
||||
updateWorkflow(id: string, workflow: Partial<Workflow>): Promise<Workflow>;
|
||||
deleteWorkflow(id: string): Promise<Workflow>;
|
||||
transferWorkflow(id: string, destinationProjectId: string): Promise<void>;
|
||||
activateWorkflow(id: string): Promise<Workflow>;
|
||||
deactivateWorkflow(id: string): Promise<Workflow>;
|
||||
listWorkflows(params?: WorkflowListParams): Promise<WorkflowListResponse>;
|
||||
@@ -44,6 +45,28 @@ export declare class N8nApiClient {
|
||||
createVariable(variable: Partial<Variable>): Promise<Variable>;
|
||||
updateVariable(id: string, variable: Partial<Variable>): Promise<Variable>;
|
||||
deleteVariable(id: string): Promise<void>;
|
||||
createDataTable(params: {
|
||||
name: string;
|
||||
columns?: DataTableColumn[];
|
||||
}): Promise<DataTable>;
|
||||
listDataTables(params?: DataTableListParams): Promise<{
|
||||
data: DataTable[];
|
||||
nextCursor?: string | null;
|
||||
}>;
|
||||
getDataTable(id: string): Promise<DataTable>;
|
||||
updateDataTable(id: string, params: {
|
||||
name: string;
|
||||
}): Promise<DataTable>;
|
||||
deleteDataTable(id: string): Promise<void>;
|
||||
getDataTableRows(id: string, params?: DataTableRowListParams): Promise<{
|
||||
data: DataTableRow[];
|
||||
nextCursor?: string | null;
|
||||
}>;
|
||||
insertDataTableRows(id: string, params: DataTableInsertRowsParams): Promise<any>;
|
||||
updateDataTableRows(id: string, params: DataTableUpdateRowsParams): Promise<any>;
|
||||
upsertDataTableRow(id: string, params: DataTableUpsertRowParams): Promise<any>;
|
||||
deleteDataTableRows(id: string, params: DataTableDeleteRowsParams): Promise<any>;
|
||||
private serializeDataTableParams;
|
||||
private validateListResponse;
|
||||
}
|
||||
//# sourceMappingURL=n8n-api-client.d.ts.map
|
||||
2
dist/services/n8n-api-client.d.ts.map
vendored
2
dist/services/n8n-api-client.d.ts.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"n8n-api-client.d.ts","sourceRoot":"","sources":["../../src/services/n8n-api-client.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"n8n-api-client.d.ts","sourceRoot":"","sources":["../../src/services/n8n-api-client.ts"],"names":[],"mappings":"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"}
|
||||
112
dist/services/n8n-api-client.js
vendored
112
dist/services/n8n-api-client.js
vendored
@@ -194,6 +194,14 @@ class N8nApiClient {
|
||||
throw (0, n8n_errors_1.handleN8nApiError)(error);
|
||||
}
|
||||
}
|
||||
async transferWorkflow(id, destinationProjectId) {
|
||||
try {
|
||||
await this.client.put(`/workflows/${id}/transfer`, { destinationProjectId });
|
||||
}
|
||||
catch (error) {
|
||||
throw (0, n8n_errors_1.handleN8nApiError)(error);
|
||||
}
|
||||
}
|
||||
async activateWorkflow(id) {
|
||||
try {
|
||||
const response = await this.client.post(`/workflows/${id}/activate`, {});
|
||||
@@ -440,6 +448,110 @@ class N8nApiClient {
|
||||
throw (0, n8n_errors_1.handleN8nApiError)(error);
|
||||
}
|
||||
}
|
||||
async createDataTable(params) {
|
||||
try {
|
||||
const response = await this.client.post('/data-tables', params);
|
||||
return response.data;
|
||||
}
|
||||
catch (error) {
|
||||
throw (0, n8n_errors_1.handleN8nApiError)(error);
|
||||
}
|
||||
}
|
||||
async listDataTables(params = {}) {
|
||||
try {
|
||||
const response = await this.client.get('/data-tables', { params });
|
||||
return this.validateListResponse(response.data, 'data-tables');
|
||||
}
|
||||
catch (error) {
|
||||
throw (0, n8n_errors_1.handleN8nApiError)(error);
|
||||
}
|
||||
}
|
||||
async getDataTable(id) {
|
||||
try {
|
||||
const response = await this.client.get(`/data-tables/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
catch (error) {
|
||||
throw (0, n8n_errors_1.handleN8nApiError)(error);
|
||||
}
|
||||
}
|
||||
async updateDataTable(id, params) {
|
||||
try {
|
||||
const response = await this.client.patch(`/data-tables/${id}`, params);
|
||||
return response.data;
|
||||
}
|
||||
catch (error) {
|
||||
throw (0, n8n_errors_1.handleN8nApiError)(error);
|
||||
}
|
||||
}
|
||||
async deleteDataTable(id) {
|
||||
try {
|
||||
await this.client.delete(`/data-tables/${id}`);
|
||||
}
|
||||
catch (error) {
|
||||
throw (0, n8n_errors_1.handleN8nApiError)(error);
|
||||
}
|
||||
}
|
||||
async getDataTableRows(id, params = {}) {
|
||||
try {
|
||||
const response = await this.client.get(`/data-tables/${id}/rows`, {
|
||||
params,
|
||||
paramsSerializer: (p) => this.serializeDataTableParams(p),
|
||||
});
|
||||
return this.validateListResponse(response.data, 'data-table-rows');
|
||||
}
|
||||
catch (error) {
|
||||
throw (0, n8n_errors_1.handleN8nApiError)(error);
|
||||
}
|
||||
}
|
||||
async insertDataTableRows(id, params) {
|
||||
try {
|
||||
const response = await this.client.post(`/data-tables/${id}/rows`, params);
|
||||
return response.data;
|
||||
}
|
||||
catch (error) {
|
||||
throw (0, n8n_errors_1.handleN8nApiError)(error);
|
||||
}
|
||||
}
|
||||
async updateDataTableRows(id, params) {
|
||||
try {
|
||||
const response = await this.client.patch(`/data-tables/${id}/rows/update`, params);
|
||||
return response.data;
|
||||
}
|
||||
catch (error) {
|
||||
throw (0, n8n_errors_1.handleN8nApiError)(error);
|
||||
}
|
||||
}
|
||||
async upsertDataTableRow(id, params) {
|
||||
try {
|
||||
const response = await this.client.post(`/data-tables/${id}/rows/upsert`, params);
|
||||
return response.data;
|
||||
}
|
||||
catch (error) {
|
||||
throw (0, n8n_errors_1.handleN8nApiError)(error);
|
||||
}
|
||||
}
|
||||
async deleteDataTableRows(id, params) {
|
||||
try {
|
||||
const response = await this.client.delete(`/data-tables/${id}/rows/delete`, {
|
||||
params,
|
||||
paramsSerializer: (p) => this.serializeDataTableParams(p),
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
catch (error) {
|
||||
throw (0, n8n_errors_1.handleN8nApiError)(error);
|
||||
}
|
||||
}
|
||||
serializeDataTableParams(params) {
|
||||
const parts = [];
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value === undefined || value === null)
|
||||
continue;
|
||||
parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
|
||||
}
|
||||
return parts.join('&');
|
||||
}
|
||||
validateListResponse(responseData, resourceType) {
|
||||
if (!responseData || typeof responseData !== 'object') {
|
||||
throw new Error(`Invalid response from n8n API for ${resourceType}: response is not an object`);
|
||||
|
||||
2
dist/services/n8n-api-client.js.map
vendored
2
dist/services/n8n-api-client.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/services/n8n-validation.d.ts.map
vendored
2
dist/services/n8n-validation.d.ts.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"n8n-validation.d.ts","sourceRoot":"","sources":["../../src/services/n8n-validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAM9E,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiB7B,CAAC;AAkBH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAUpC,CAAC;AAEF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAWjC,CAAC;AAGH,eAAO,MAAM,uBAAuB;;;;;;CAMnC,CAAC;AAGF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,YAAY,CAEhE;AAED,wBAAgB,2BAA2B,CAAC,WAAW,EAAE,OAAO,GAAG,kBAAkB,CAEpF;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAElG;AAGD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,CAsBrF;AAiBD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAoE5E;AAGD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,MAAM,EAAE,CAkQ/E;AAGD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAK7D;AAMD,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,EAAE,CA+F5E;AAMD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CA0D/E;AAGD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAmB/D;AAGD,wBAAgB,2BAA2B,IAAI,MAAM,CA6CpD;AAGD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAmBpE"}
|
||||
{"version":3,"file":"n8n-validation.d.ts","sourceRoot":"","sources":["../../src/services/n8n-validation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAM9E,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiB7B,CAAC;AAkBH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAUpC,CAAC;AAEF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAWjC,CAAC;AAGH,eAAO,MAAM,uBAAuB;;;;;;CAMnC,CAAC;AAGF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,YAAY,CAEhE;AAED,wBAAgB,2BAA2B,CAAC,WAAW,EAAE,OAAO,GAAG,kBAAkB,CAEpF;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAElG;AAmBD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,CAwBrF;AAiBD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAsE5E;AAGD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,MAAM,EAAE,CAkQ/E;AAGD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAK7D;AAMD,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,EAAE,CA+F5E;AAMD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CA0D/E;AAGD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAmB/D;AAGD,wBAAgB,2BAA2B,IAAI,MAAM,CA6CpD;AAGD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAmBpE"}
|
||||
21
dist/services/n8n-validation.js
vendored
21
dist/services/n8n-validation.js
vendored
@@ -1,4 +1,7 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.defaultWorkflowSettings = exports.workflowSettingsSchema = exports.workflowConnectionSchema = exports.workflowNodeSchema = void 0;
|
||||
exports.validateWorkflowNode = validateWorkflowNode;
|
||||
@@ -13,6 +16,7 @@ exports.validateOperatorStructure = validateOperatorStructure;
|
||||
exports.getWebhookUrl = getWebhookUrl;
|
||||
exports.getWorkflowStructureExample = getWorkflowStructureExample;
|
||||
exports.getWorkflowFixSuggestions = getWorkflowFixSuggestions;
|
||||
const crypto_1 = __importDefault(require("crypto"));
|
||||
const zod_1 = require("zod");
|
||||
const node_type_utils_1 = require("../utils/node-type-utils");
|
||||
const node_classification_1 = require("../utils/node-classification");
|
||||
@@ -76,11 +80,27 @@ function validateWorkflowConnections(connections) {
|
||||
function validateWorkflowSettings(settings) {
|
||||
return exports.workflowSettingsSchema.parse(settings);
|
||||
}
|
||||
const WEBHOOK_NODE_TYPES = new Set([
|
||||
'n8n-nodes-base.webhook',
|
||||
'n8n-nodes-base.webhookTrigger',
|
||||
'n8n-nodes-base.formTrigger',
|
||||
'@n8n/n8n-nodes-langchain.chatTrigger',
|
||||
]);
|
||||
function ensureWebhookIds(nodes) {
|
||||
if (!nodes)
|
||||
return;
|
||||
for (const node of nodes) {
|
||||
if (WEBHOOK_NODE_TYPES.has(node.type) && !node.webhookId) {
|
||||
node.webhookId = crypto_1.default.randomUUID();
|
||||
}
|
||||
}
|
||||
}
|
||||
function cleanWorkflowForCreate(workflow) {
|
||||
const { id, createdAt, updatedAt, versionId, meta, active, tags, ...cleanedWorkflow } = workflow;
|
||||
if (!cleanedWorkflow.settings || Object.keys(cleanedWorkflow.settings).length === 0) {
|
||||
cleanedWorkflow.settings = exports.defaultWorkflowSettings;
|
||||
}
|
||||
ensureWebhookIds(cleanedWorkflow.nodes);
|
||||
return cleanedWorkflow;
|
||||
}
|
||||
function cleanWorkflowForUpdate(workflow) {
|
||||
@@ -116,6 +136,7 @@ function cleanWorkflowForUpdate(workflow) {
|
||||
else {
|
||||
cleanedWorkflow.settings = { executionOrder: 'v1' };
|
||||
}
|
||||
ensureWebhookIds(cleanedWorkflow.nodes);
|
||||
return cleanedWorkflow;
|
||||
}
|
||||
function validateWorkflowStructure(workflow) {
|
||||
|
||||
2
dist/services/n8n-validation.js.map
vendored
2
dist/services/n8n-validation.js.map
vendored
File diff suppressed because one or more lines are too long
3
dist/services/workflow-diff-engine.d.ts
vendored
3
dist/services/workflow-diff-engine.d.ts
vendored
@@ -7,6 +7,7 @@ export declare class WorkflowDiffEngine {
|
||||
private removedNodeNames;
|
||||
private tagsToAdd;
|
||||
private tagsToRemove;
|
||||
private transferToProjectId;
|
||||
applyDiff(workflow: Workflow, request: WorkflowDiffRequest): Promise<WorkflowDiffResult>;
|
||||
private validateOperation;
|
||||
private applyOperation;
|
||||
@@ -36,6 +37,8 @@ export declare class WorkflowDiffEngine {
|
||||
private validateDeactivateWorkflow;
|
||||
private applyActivateWorkflow;
|
||||
private applyDeactivateWorkflow;
|
||||
private validateTransferWorkflow;
|
||||
private applyTransferWorkflow;
|
||||
private validateCleanStaleConnections;
|
||||
private validateReplaceConnections;
|
||||
private applyCleanStaleConnections;
|
||||
|
||||
2
dist/services/workflow-diff-engine.d.ts.map
vendored
2
dist/services/workflow-diff-engine.d.ts.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"workflow-diff-engine.d.ts","sourceRoot":"","sources":["../../src/services/workflow-diff-engine.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,mBAAmB,EACnB,kBAAkB,EAsBnB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAoC,MAAM,kBAAkB,CAAC;AAY9E,qBAAa,kBAAkB;IAE7B,OAAO,CAAC,SAAS,CAAkC;IAEnD,OAAO,CAAC,QAAQ,CAAqC;IAErD,OAAO,CAAC,eAAe,CAAqB;IAE5C,OAAO,CAAC,gBAAgB,CAAqB;IAE7C,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,YAAY,CAAgB;IAK9B,SAAS,CACb,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,kBAAkB,CAAC;IAqN9B,OAAO,CAAC,iBAAiB;IAwCzB,OAAO,CAAC,cAAc;IAyDtB,OAAO,CAAC,eAAe;IAwBvB,OAAO,CAAC,kBAAkB;IAuB1B,OAAO,CAAC,kBAAkB;IAoC1B,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,qBAAqB;IAkD7B,OAAO,CAAC,wBAAwB;IA6ChC,OAAO,CAAC,wBAAwB;IAmDhC,OAAO,CAAC,YAAY;IA4BpB,OAAO,CAAC,eAAe;IAwCvB,OAAO,CAAC,eAAe;IA0BvB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,sBAAsB;IAwD9B,OAAO,CAAC,kBAAkB;IA6C1B,OAAO,CAAC,qBAAqB;IAuC7B,OAAO,CAAC,qBAAqB;IA0B7B,OAAO,CAAC,mBAAmB;IAW3B,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,WAAW;IAYnB,OAAO,CAAC,cAAc;IAatB,OAAO,CAAC,wBAAwB;IAchC,OAAO,CAAC,0BAA0B;IAMlC,OAAO,CAAC,qBAAqB;IAM7B,OAAO,CAAC,uBAAuB;IAO/B,OAAO,CAAC,6BAA6B;IAKrC,OAAO,CAAC,0BAA0B;IA0BlC,OAAO,CAAC,0BAA0B;IA+ElC,OAAO,CAAC,uBAAuB;IAe/B,OAAO,CAAC,0BAA0B;IAkElC,OAAO,CAAC,iBAAiB;IAkBzB,OAAO,CAAC,QAAQ;IAsChB,OAAO,CAAC,uBAAuB;IAW/B,OAAO,CAAC,iBAAiB;CAoB1B"}
|
||||
{"version":3,"file":"workflow-diff-engine.d.ts","sourceRoot":"","sources":["../../src/services/workflow-diff-engine.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,mBAAmB,EACnB,kBAAkB,EAuBnB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAoC,MAAM,kBAAkB,CAAC;AAY9E,qBAAa,kBAAkB;IAE7B,OAAO,CAAC,SAAS,CAAkC;IAEnD,OAAO,CAAC,QAAQ,CAAqC;IAErD,OAAO,CAAC,eAAe,CAAqB;IAE5C,OAAO,CAAC,gBAAgB,CAAqB;IAE7C,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,YAAY,CAAgB;IAEpC,OAAO,CAAC,mBAAmB,CAAqB;IAK1C,SAAS,CACb,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,kBAAkB,CAAC;IAgO9B,OAAO,CAAC,iBAAiB;IA0CzB,OAAO,CAAC,cAAc;IA4DtB,OAAO,CAAC,eAAe;IAwBvB,OAAO,CAAC,kBAAkB;IAuB1B,OAAO,CAAC,kBAAkB;IAoC1B,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,qBAAqB;IAkD7B,OAAO,CAAC,wBAAwB;IA6ChC,OAAO,CAAC,wBAAwB;IAmDhC,OAAO,CAAC,YAAY;IA4BpB,OAAO,CAAC,eAAe;IAwCvB,OAAO,CAAC,eAAe;IA0BvB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,sBAAsB;IAwD9B,OAAO,CAAC,kBAAkB;IA6C1B,OAAO,CAAC,qBAAqB;IAuC7B,OAAO,CAAC,qBAAqB;IA0B7B,OAAO,CAAC,mBAAmB;IAW3B,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,WAAW;IAYnB,OAAO,CAAC,cAAc;IAatB,OAAO,CAAC,wBAAwB;IAchC,OAAO,CAAC,0BAA0B;IAMlC,OAAO,CAAC,qBAAqB;IAM7B,OAAO,CAAC,uBAAuB;IAO/B,OAAO,CAAC,wBAAwB;IAOhC,OAAO,CAAC,qBAAqB;IAK7B,OAAO,CAAC,6BAA6B;IAKrC,OAAO,CAAC,0BAA0B;IA0BlC,OAAO,CAAC,0BAA0B;IA+ElC,OAAO,CAAC,uBAAuB;IAe/B,OAAO,CAAC,0BAA0B;IAmElC,OAAO,CAAC,iBAAiB;IAkBzB,OAAO,CAAC,QAAQ;IAsChB,OAAO,CAAC,uBAAuB;IAW/B,OAAO,CAAC,iBAAiB;CAoB1B"}
|
||||
30
dist/services/workflow-diff-engine.js
vendored
30
dist/services/workflow-diff-engine.js
vendored
@@ -23,6 +23,7 @@ class WorkflowDiffEngine {
|
||||
this.removedNodeNames.clear();
|
||||
this.tagsToAdd = [];
|
||||
this.tagsToRemove = [];
|
||||
this.transferToProjectId = undefined;
|
||||
const workflowCopy = JSON.parse(JSON.stringify(workflow));
|
||||
const nodeOperationTypes = ['addNode', 'removeNode', 'updateNode', 'moveNode', 'enableNode', 'disableNode'];
|
||||
const nodeOperations = [];
|
||||
@@ -81,6 +82,10 @@ class WorkflowDiffEngine {
|
||||
failed: failedIndices
|
||||
};
|
||||
}
|
||||
const shouldActivate = workflowCopy._shouldActivate === true;
|
||||
const shouldDeactivate = workflowCopy._shouldDeactivate === true;
|
||||
delete workflowCopy._shouldActivate;
|
||||
delete workflowCopy._shouldDeactivate;
|
||||
const success = appliedIndices.length > 0;
|
||||
return {
|
||||
success,
|
||||
@@ -91,8 +96,11 @@ class WorkflowDiffEngine {
|
||||
warnings: this.warnings.length > 0 ? this.warnings : undefined,
|
||||
applied: appliedIndices,
|
||||
failed: failedIndices,
|
||||
shouldActivate: shouldActivate || undefined,
|
||||
shouldDeactivate: shouldDeactivate || undefined,
|
||||
tagsToAdd: this.tagsToAdd.length > 0 ? this.tagsToAdd : undefined,
|
||||
tagsToRemove: this.tagsToRemove.length > 0 ? this.tagsToRemove : undefined
|
||||
tagsToRemove: this.tagsToRemove.length > 0 ? this.tagsToRemove : undefined,
|
||||
transferToProjectId: this.transferToProjectId || undefined
|
||||
};
|
||||
}
|
||||
else {
|
||||
@@ -181,7 +189,8 @@ class WorkflowDiffEngine {
|
||||
shouldActivate: shouldActivate || undefined,
|
||||
shouldDeactivate: shouldDeactivate || undefined,
|
||||
tagsToAdd: this.tagsToAdd.length > 0 ? this.tagsToAdd : undefined,
|
||||
tagsToRemove: this.tagsToRemove.length > 0 ? this.tagsToRemove : undefined
|
||||
tagsToRemove: this.tagsToRemove.length > 0 ? this.tagsToRemove : undefined,
|
||||
transferToProjectId: this.transferToProjectId || undefined
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -220,6 +229,8 @@ class WorkflowDiffEngine {
|
||||
case 'addTag':
|
||||
case 'removeTag':
|
||||
return null;
|
||||
case 'transferWorkflow':
|
||||
return this.validateTransferWorkflow(workflow, operation);
|
||||
case 'activateWorkflow':
|
||||
return this.validateActivateWorkflow(workflow, operation);
|
||||
case 'deactivateWorkflow':
|
||||
@@ -285,6 +296,9 @@ class WorkflowDiffEngine {
|
||||
case 'replaceConnections':
|
||||
this.applyReplaceConnections(workflow, operation);
|
||||
break;
|
||||
case 'transferWorkflow':
|
||||
this.applyTransferWorkflow(workflow, operation);
|
||||
break;
|
||||
}
|
||||
}
|
||||
validateAddNode(workflow, operation) {
|
||||
@@ -699,6 +713,15 @@ class WorkflowDiffEngine {
|
||||
applyDeactivateWorkflow(workflow, operation) {
|
||||
workflow._shouldDeactivate = true;
|
||||
}
|
||||
validateTransferWorkflow(_workflow, operation) {
|
||||
if (!operation.destinationProjectId) {
|
||||
return 'transferWorkflow requires a non-empty destinationProjectId string';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
applyTransferWorkflow(_workflow, operation) {
|
||||
this.transferToProjectId = operation.destinationProjectId;
|
||||
}
|
||||
validateCleanStaleConnections(workflow, operation) {
|
||||
return null;
|
||||
}
|
||||
@@ -806,9 +829,10 @@ class WorkflowDiffEngine {
|
||||
for (let connIndex = 0; connIndex < connectionsAtIndex.length; connIndex++) {
|
||||
const connection = connectionsAtIndex[connIndex];
|
||||
if (renames.has(connection.node)) {
|
||||
const oldTargetName = connection.node;
|
||||
const newTargetName = renames.get(connection.node);
|
||||
connection.node = newTargetName;
|
||||
logger.debug(`Updated connection: ${sourceName}[${outputType}][${outputIndex}][${connIndex}].node: "${connection.node}" → "${newTargetName}"`);
|
||||
logger.debug(`Updated connection: ${sourceName}[${outputType}][${outputIndex}][${connIndex}].node: "${oldTargetName}" → "${newTargetName}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
dist/services/workflow-diff-engine.js.map
vendored
2
dist/services/workflow-diff-engine.js.map
vendored
File diff suppressed because one or more lines are too long
65
dist/types/n8n-api.d.ts
vendored
65
dist/types/n8n-api.d.ts
vendored
@@ -374,4 +374,69 @@ export interface ErrorSuggestion {
|
||||
description: string;
|
||||
confidence: 'high' | 'medium' | 'low';
|
||||
}
|
||||
export interface DataTableColumn {
|
||||
name: string;
|
||||
type?: 'string' | 'number' | 'boolean' | 'date';
|
||||
}
|
||||
export interface DataTableColumnResponse {
|
||||
id: string;
|
||||
name: string;
|
||||
type: 'string' | 'number' | 'boolean' | 'date';
|
||||
index: number;
|
||||
}
|
||||
export interface DataTable {
|
||||
id: string;
|
||||
name: string;
|
||||
columns?: DataTableColumnResponse[];
|
||||
projectId?: string;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
export interface DataTableRow {
|
||||
id?: number;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
[columnName: string]: unknown;
|
||||
}
|
||||
export interface DataTableFilterCondition {
|
||||
columnName: string;
|
||||
condition: 'eq' | 'neq' | 'like' | 'ilike' | 'gt' | 'gte' | 'lt' | 'lte';
|
||||
value?: any;
|
||||
}
|
||||
export interface DataTableFilter {
|
||||
type?: 'and' | 'or';
|
||||
filters: DataTableFilterCondition[];
|
||||
}
|
||||
export interface DataTableListParams {
|
||||
limit?: number;
|
||||
cursor?: string;
|
||||
}
|
||||
export interface DataTableRowListParams {
|
||||
limit?: number;
|
||||
cursor?: string;
|
||||
filter?: string;
|
||||
sortBy?: string;
|
||||
search?: string;
|
||||
}
|
||||
export interface DataTableInsertRowsParams {
|
||||
data: Record<string, unknown>[];
|
||||
returnType?: 'count' | 'id' | 'all';
|
||||
}
|
||||
export interface DataTableUpdateRowsParams {
|
||||
filter: DataTableFilter;
|
||||
data: Record<string, unknown>;
|
||||
returnData?: boolean;
|
||||
dryRun?: boolean;
|
||||
}
|
||||
export interface DataTableUpsertRowParams {
|
||||
filter: DataTableFilter;
|
||||
data: Record<string, unknown>;
|
||||
returnData?: boolean;
|
||||
dryRun?: boolean;
|
||||
}
|
||||
export interface DataTableDeleteRowsParams {
|
||||
filter: string;
|
||||
returnData?: boolean;
|
||||
dryRun?: boolean;
|
||||
}
|
||||
//# sourceMappingURL=n8n-api.d.ts.map
|
||||
2
dist/types/n8n-api.d.ts.map
vendored
2
dist/types/n8n-api.d.ts.map
vendored
File diff suppressed because one or more lines are too long
7
dist/types/workflow-diff.d.ts
vendored
7
dist/types/workflow-diff.d.ts
vendored
@@ -94,6 +94,10 @@ export interface ActivateWorkflowOperation extends DiffOperation {
|
||||
export interface DeactivateWorkflowOperation extends DiffOperation {
|
||||
type: 'deactivateWorkflow';
|
||||
}
|
||||
export interface TransferWorkflowOperation extends DiffOperation {
|
||||
type: 'transferWorkflow';
|
||||
destinationProjectId: string;
|
||||
}
|
||||
export interface CleanStaleConnectionsOperation extends DiffOperation {
|
||||
type: 'cleanStaleConnections';
|
||||
dryRun?: boolean;
|
||||
@@ -110,7 +114,7 @@ export interface ReplaceConnectionsOperation extends DiffOperation {
|
||||
};
|
||||
};
|
||||
}
|
||||
export type WorkflowDiffOperation = AddNodeOperation | RemoveNodeOperation | UpdateNodeOperation | MoveNodeOperation | EnableNodeOperation | DisableNodeOperation | AddConnectionOperation | RemoveConnectionOperation | RewireConnectionOperation | UpdateSettingsOperation | UpdateNameOperation | AddTagOperation | RemoveTagOperation | ActivateWorkflowOperation | DeactivateWorkflowOperation | CleanStaleConnectionsOperation | ReplaceConnectionsOperation;
|
||||
export type WorkflowDiffOperation = AddNodeOperation | RemoveNodeOperation | UpdateNodeOperation | MoveNodeOperation | EnableNodeOperation | DisableNodeOperation | AddConnectionOperation | RemoveConnectionOperation | RewireConnectionOperation | UpdateSettingsOperation | UpdateNameOperation | AddTagOperation | RemoveTagOperation | ActivateWorkflowOperation | DeactivateWorkflowOperation | CleanStaleConnectionsOperation | ReplaceConnectionsOperation | TransferWorkflowOperation;
|
||||
export interface WorkflowDiffRequest {
|
||||
id: string;
|
||||
operations: WorkflowDiffOperation[];
|
||||
@@ -139,6 +143,7 @@ export interface WorkflowDiffResult {
|
||||
shouldDeactivate?: boolean;
|
||||
tagsToAdd?: string[];
|
||||
tagsToRemove?: string[];
|
||||
transferToProjectId?: string;
|
||||
}
|
||||
export interface NodeReference {
|
||||
id?: string;
|
||||
|
||||
2
dist/types/workflow-diff.d.ts.map
vendored
2
dist/types/workflow-diff.d.ts.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"workflow-diff.d.ts","sourceRoot":"","sources":["../../src/types/workflow-diff.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"workflow-diff.d.ts","sourceRoot":"","sources":["../../src/types/workflow-diff.ts"],"names":[],"mappings":"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"}
|
||||
2
dist/types/workflow-diff.js.map
vendored
2
dist/types/workflow-diff.js.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"workflow-diff.js","sourceRoot":"","sources":["../../src/types/workflow-diff.ts"],"names":[],"mappings":";;AA2MA,0CAIC;AAED,sDAGC;AAED,kDAGC;AAdD,SAAgB,eAAe,CAAC,EAAyB;IAGvD,OAAO,CAAC,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;AAC5G,CAAC;AAED,SAAgB,qBAAqB,CAAC,EAAyB;IAE7D,OAAO,CAAC,eAAe,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,oBAAoB,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;AACpI,CAAC;AAED,SAAgB,mBAAmB,CAAC,EAAyB;IAE3D,OAAO,CAAC,gBAAgB,EAAE,YAAY,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;AACnF,CAAC"}
|
||||
{"version":3,"file":"workflow-diff.js","sourceRoot":"","sources":["../../src/types/workflow-diff.ts"],"names":[],"mappings":";;AAkNA,0CAIC;AAED,sDAGC;AAED,kDAGC;AAdD,SAAgB,eAAe,CAAC,EAAyB;IAGvD,OAAO,CAAC,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;AAC5G,CAAC;AAED,SAAgB,qBAAqB,CAAC,EAAyB;IAE7D,OAAO,CAAC,eAAe,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,oBAAoB,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;AACpI,CAAC;AAED,SAAgB,mBAAmB,CAAC,EAAyB;IAE3D,OAAO,CAAC,gBAAgB,EAAE,YAAY,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;AACnF,CAAC"}
|
||||
2
dist/utils/n8n-errors.d.ts
vendored
2
dist/utils/n8n-errors.d.ts
vendored
@@ -8,7 +8,7 @@ export declare class N8nAuthenticationError extends N8nApiError {
|
||||
constructor(message?: string);
|
||||
}
|
||||
export declare class N8nNotFoundError extends N8nApiError {
|
||||
constructor(resource: string, id?: string);
|
||||
constructor(messageOrResource: string, id?: string);
|
||||
}
|
||||
export declare class N8nValidationError extends N8nApiError {
|
||||
constructor(message: string, details?: unknown);
|
||||
|
||||
2
dist/utils/n8n-errors.d.ts.map
vendored
2
dist/utils/n8n-errors.d.ts.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"n8n-errors.d.ts","sourceRoot":"","sources":["../../src/utils/n8n-errors.ts"],"names":[],"mappings":"AAIA,qBAAa,WAAY,SAAQ,KAAK;IAG3B,UAAU,CAAC,EAAE,MAAM;IACnB,IAAI,CAAC,EAAE,MAAM;IACb,OAAO,CAAC,EAAE,OAAO;gBAHxB,OAAO,EAAE,MAAM,EACR,UAAU,CAAC,EAAE,MAAM,YAAA,EACnB,IAAI,CAAC,EAAE,MAAM,YAAA,EACb,OAAO,CAAC,EAAE,OAAO,YAAA;CAK3B;AAED,qBAAa,sBAAuB,SAAQ,WAAW;gBACzC,OAAO,SAA0B;CAI9C;AAED,qBAAa,gBAAiB,SAAQ,WAAW;gBACnC,QAAQ,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM;CAK1C;AAED,qBAAa,kBAAmB,SAAQ,WAAW;gBACrC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO;CAI/C;AAED,qBAAa,iBAAkB,SAAQ,WAAW;gBACpC,UAAU,CAAC,EAAE,MAAM;CAOhC;AAED,qBAAa,cAAe,SAAQ,WAAW;gBACjC,OAAO,SAA0B,EAAE,UAAU,SAAM;CAIhE;AAGD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAuC7D;AAQD,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAGrF;AAMD,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C;AAGD,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAmBtE;AAGD,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAiBtE"}
|
||||
{"version":3,"file":"n8n-errors.d.ts","sourceRoot":"","sources":["../../src/utils/n8n-errors.ts"],"names":[],"mappings":"AAIA,qBAAa,WAAY,SAAQ,KAAK;IAG3B,UAAU,CAAC,EAAE,MAAM;IACnB,IAAI,CAAC,EAAE,MAAM;IACb,OAAO,CAAC,EAAE,OAAO;gBAHxB,OAAO,EAAE,MAAM,EACR,UAAU,CAAC,EAAE,MAAM,YAAA,EACnB,IAAI,CAAC,EAAE,MAAM,YAAA,EACb,OAAO,CAAC,EAAE,OAAO,YAAA;CAK3B;AAED,qBAAa,sBAAuB,SAAQ,WAAW;gBACzC,OAAO,SAA0B;CAI9C;AAED,qBAAa,gBAAiB,SAAQ,WAAW;gBACnC,iBAAiB,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM;CAOnD;AAED,qBAAa,kBAAmB,SAAQ,WAAW;gBACrC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO;CAI/C;AAED,qBAAa,iBAAkB,SAAQ,WAAW;gBACpC,UAAU,CAAC,EAAE,MAAM;CAOhC;AAED,qBAAa,cAAe,SAAQ,WAAW;gBACjC,OAAO,SAA0B,EAAE,UAAU,SAAM;CAIhE;AAGD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAuC7D;AAQD,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAGrF;AAMD,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C;AAGD,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAmBtE;AAGD,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAiBtE"}
|
||||
6
dist/utils/n8n-errors.js
vendored
6
dist/utils/n8n-errors.js
vendored
@@ -25,8 +25,8 @@ class N8nAuthenticationError extends N8nApiError {
|
||||
}
|
||||
exports.N8nAuthenticationError = N8nAuthenticationError;
|
||||
class N8nNotFoundError extends N8nApiError {
|
||||
constructor(resource, id) {
|
||||
const message = id ? `${resource} with ID ${id} not found` : `${resource} not found`;
|
||||
constructor(messageOrResource, id) {
|
||||
const message = id ? `${messageOrResource} with ID ${id} not found` : messageOrResource;
|
||||
super(message, 404, 'NOT_FOUND');
|
||||
this.name = 'N8nNotFoundError';
|
||||
}
|
||||
@@ -69,7 +69,7 @@ function handleN8nApiError(error) {
|
||||
case 401:
|
||||
return new N8nAuthenticationError(message);
|
||||
case 404:
|
||||
return new N8nNotFoundError('Resource', message);
|
||||
return new N8nNotFoundError(message || 'Resource');
|
||||
case 400:
|
||||
return new N8nValidationError(message, data);
|
||||
case 429:
|
||||
|
||||
2
dist/utils/n8n-errors.js.map
vendored
2
dist/utils/n8n-errors.js.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"n8n-errors.js","sourceRoot":"","sources":["../../src/utils/n8n-errors.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"n8n-errors.js","sourceRoot":"","sources":["../../src/utils/n8n-errors.ts"],"names":[],"mappings":"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"}
|
||||
9017
package-lock.json
generated
9017
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n-mcp",
|
||||
"version": "2.37.4",
|
||||
"version": "2.41.0",
|
||||
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
@@ -92,10 +92,6 @@
|
||||
"test:docker:security": "./scripts/test-docker-config.sh security",
|
||||
"sanitize:templates": "node dist/scripts/sanitize-templates.js",
|
||||
"db:rebuild": "node dist/scripts/rebuild-database.js",
|
||||
"benchmark": "vitest bench --config vitest.config.benchmark.ts",
|
||||
"benchmark:watch": "vitest bench --watch --config vitest.config.benchmark.ts",
|
||||
"benchmark:ui": "vitest bench --ui --config vitest.config.benchmark.ts",
|
||||
"benchmark:ci": "CI=true node scripts/run-benchmarks-ci.js",
|
||||
"db:init": "node -e \"new (require('./dist/services/sqlite-storage-service').SQLiteStorageService)(); console.log('Database initialized')\"",
|
||||
"docs:rebuild": "ts-node src/scripts/rebuild-database.ts",
|
||||
"sync:runtime-version": "node scripts/sync-runtime-version.js",
|
||||
@@ -152,17 +148,17 @@
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.27.1",
|
||||
"@n8n/n8n-nodes-langchain": "^2.12.0",
|
||||
"@modelcontextprotocol/sdk": "1.28.0",
|
||||
"@n8n/n8n-nodes-langchain": "^2.13.1",
|
||||
"@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.12.3",
|
||||
"n8n-core": "^2.12.0",
|
||||
"n8n-workflow": "^2.12.0",
|
||||
"n8n": "^2.13.3",
|
||||
"n8n-core": "^2.13.1",
|
||||
"n8n-workflow": "^2.13.1",
|
||||
"openai": "^4.77.0",
|
||||
"sql.js": "^1.13.0",
|
||||
"tslib": "^2.6.2",
|
||||
|
||||
@@ -1,260 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
import { readFileSync, existsSync, writeFileSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
|
||||
/**
|
||||
* Compare benchmark results between runs
|
||||
*/
|
||||
class BenchmarkComparator {
|
||||
constructor() {
|
||||
this.threshold = 0.1; // 10% threshold for significant changes
|
||||
}
|
||||
|
||||
loadBenchmarkResults(path) {
|
||||
if (!existsSync(path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(readFileSync(path, 'utf-8'));
|
||||
} catch (error) {
|
||||
console.error(`Error loading benchmark results from ${path}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
compareBenchmarks(current, baseline) {
|
||||
const comparison = {
|
||||
timestamp: new Date().toISOString(),
|
||||
summary: {
|
||||
improved: 0,
|
||||
regressed: 0,
|
||||
unchanged: 0,
|
||||
added: 0,
|
||||
removed: 0
|
||||
},
|
||||
benchmarks: []
|
||||
};
|
||||
|
||||
// Create maps for easy lookup
|
||||
const currentMap = new Map();
|
||||
const baselineMap = new Map();
|
||||
|
||||
// Process current benchmarks
|
||||
if (current && current.files) {
|
||||
for (const file of current.files) {
|
||||
for (const group of file.groups || []) {
|
||||
for (const bench of group.benchmarks || []) {
|
||||
const key = `${group.name}::${bench.name}`;
|
||||
currentMap.set(key, {
|
||||
ops: bench.result.hz,
|
||||
mean: bench.result.mean,
|
||||
file: file.filepath
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process baseline benchmarks
|
||||
if (baseline && baseline.files) {
|
||||
for (const file of baseline.files) {
|
||||
for (const group of file.groups || []) {
|
||||
for (const bench of group.benchmarks || []) {
|
||||
const key = `${group.name}::${bench.name}`;
|
||||
baselineMap.set(key, {
|
||||
ops: bench.result.hz,
|
||||
mean: bench.result.mean,
|
||||
file: file.filepath
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compare benchmarks
|
||||
for (const [key, current] of currentMap) {
|
||||
const baseline = baselineMap.get(key);
|
||||
|
||||
if (!baseline) {
|
||||
// New benchmark
|
||||
comparison.summary.added++;
|
||||
comparison.benchmarks.push({
|
||||
name: key,
|
||||
status: 'added',
|
||||
current: current.ops,
|
||||
baseline: null,
|
||||
change: null,
|
||||
file: current.file
|
||||
});
|
||||
} else {
|
||||
// Compare performance
|
||||
const change = ((current.ops - baseline.ops) / baseline.ops) * 100;
|
||||
let status = 'unchanged';
|
||||
|
||||
if (Math.abs(change) >= this.threshold * 100) {
|
||||
if (change > 0) {
|
||||
status = 'improved';
|
||||
comparison.summary.improved++;
|
||||
} else {
|
||||
status = 'regressed';
|
||||
comparison.summary.regressed++;
|
||||
}
|
||||
} else {
|
||||
comparison.summary.unchanged++;
|
||||
}
|
||||
|
||||
comparison.benchmarks.push({
|
||||
name: key,
|
||||
status,
|
||||
current: current.ops,
|
||||
baseline: baseline.ops,
|
||||
change,
|
||||
meanCurrent: current.mean,
|
||||
meanBaseline: baseline.mean,
|
||||
file: current.file
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Check for removed benchmarks
|
||||
for (const [key, baseline] of baselineMap) {
|
||||
if (!currentMap.has(key)) {
|
||||
comparison.summary.removed++;
|
||||
comparison.benchmarks.push({
|
||||
name: key,
|
||||
status: 'removed',
|
||||
current: null,
|
||||
baseline: baseline.ops,
|
||||
change: null,
|
||||
file: baseline.file
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by change percentage (regressions first)
|
||||
comparison.benchmarks.sort((a, b) => {
|
||||
if (a.status === 'regressed' && b.status !== 'regressed') return -1;
|
||||
if (b.status === 'regressed' && a.status !== 'regressed') return 1;
|
||||
if (a.change !== null && b.change !== null) {
|
||||
return a.change - b.change;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
return comparison;
|
||||
}
|
||||
|
||||
generateMarkdownReport(comparison) {
|
||||
let report = '## Benchmark Comparison Report\n\n';
|
||||
|
||||
const { summary } = comparison;
|
||||
report += '### Summary\n\n';
|
||||
report += `- **Improved**: ${summary.improved} benchmarks\n`;
|
||||
report += `- **Regressed**: ${summary.regressed} benchmarks\n`;
|
||||
report += `- **Unchanged**: ${summary.unchanged} benchmarks\n`;
|
||||
report += `- **Added**: ${summary.added} benchmarks\n`;
|
||||
report += `- **Removed**: ${summary.removed} benchmarks\n\n`;
|
||||
|
||||
// Regressions
|
||||
const regressions = comparison.benchmarks.filter(b => b.status === 'regressed');
|
||||
if (regressions.length > 0) {
|
||||
report += '### ⚠️ Performance Regressions\n\n';
|
||||
report += '| Benchmark | Current | Baseline | Change |\n';
|
||||
report += '|-----------|---------|----------|--------|\n';
|
||||
|
||||
for (const bench of regressions) {
|
||||
const currentOps = bench.current.toLocaleString('en-US', { maximumFractionDigits: 0 });
|
||||
const baselineOps = bench.baseline.toLocaleString('en-US', { maximumFractionDigits: 0 });
|
||||
const changeStr = bench.change.toFixed(2);
|
||||
report += `| ${bench.name} | ${currentOps} ops/s | ${baselineOps} ops/s | **${changeStr}%** |\n`;
|
||||
}
|
||||
report += '\n';
|
||||
}
|
||||
|
||||
// Improvements
|
||||
const improvements = comparison.benchmarks.filter(b => b.status === 'improved');
|
||||
if (improvements.length > 0) {
|
||||
report += '### ✅ Performance Improvements\n\n';
|
||||
report += '| Benchmark | Current | Baseline | Change |\n';
|
||||
report += '|-----------|---------|----------|--------|\n';
|
||||
|
||||
for (const bench of improvements) {
|
||||
const currentOps = bench.current.toLocaleString('en-US', { maximumFractionDigits: 0 });
|
||||
const baselineOps = bench.baseline.toLocaleString('en-US', { maximumFractionDigits: 0 });
|
||||
const changeStr = bench.change.toFixed(2);
|
||||
report += `| ${bench.name} | ${currentOps} ops/s | ${baselineOps} ops/s | **+${changeStr}%** |\n`;
|
||||
}
|
||||
report += '\n';
|
||||
}
|
||||
|
||||
// New benchmarks
|
||||
const added = comparison.benchmarks.filter(b => b.status === 'added');
|
||||
if (added.length > 0) {
|
||||
report += '### 🆕 New Benchmarks\n\n';
|
||||
report += '| Benchmark | Performance |\n';
|
||||
report += '|-----------|-------------|\n';
|
||||
|
||||
for (const bench of added) {
|
||||
const ops = bench.current.toLocaleString('en-US', { maximumFractionDigits: 0 });
|
||||
report += `| ${bench.name} | ${ops} ops/s |\n`;
|
||||
}
|
||||
report += '\n';
|
||||
}
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
generateJsonReport(comparison) {
|
||||
return JSON.stringify(comparison, null, 2);
|
||||
}
|
||||
|
||||
async compare(currentPath, baselinePath) {
|
||||
// Load results
|
||||
const current = this.loadBenchmarkResults(currentPath);
|
||||
const baseline = this.loadBenchmarkResults(baselinePath);
|
||||
|
||||
if (!current && !baseline) {
|
||||
console.error('No benchmark results found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate comparison
|
||||
const comparison = this.compareBenchmarks(current, baseline);
|
||||
|
||||
// Generate reports
|
||||
const markdownReport = this.generateMarkdownReport(comparison);
|
||||
const jsonReport = this.generateJsonReport(comparison);
|
||||
|
||||
// Write reports
|
||||
writeFileSync('benchmark-comparison.md', markdownReport);
|
||||
writeFileSync('benchmark-comparison.json', jsonReport);
|
||||
|
||||
// Output summary to console
|
||||
console.log(markdownReport);
|
||||
|
||||
// Return exit code based on regressions
|
||||
if (comparison.summary.regressed > 0) {
|
||||
console.error(`\n❌ Found ${comparison.summary.regressed} performance regressions`);
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log(`\n✅ No performance regressions found`);
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse command line arguments
|
||||
const args = process.argv.slice(2);
|
||||
if (args.length < 1) {
|
||||
console.error('Usage: node compare-benchmarks.js <current-results> [baseline-results]');
|
||||
console.error('If baseline-results is not provided, it will look for benchmark-baseline.json');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const currentPath = args[0];
|
||||
const baselinePath = args[1] || 'benchmark-baseline.json';
|
||||
|
||||
// Run comparison
|
||||
const comparator = new BenchmarkComparator();
|
||||
comparator.compare(currentPath, baselinePath).catch(console.error);
|
||||
@@ -1,86 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* Formats Vitest benchmark results for github-action-benchmark
|
||||
* Converts from Vitest format to the expected format
|
||||
*/
|
||||
function formatBenchmarkResults() {
|
||||
const resultsPath = path.join(process.cwd(), 'benchmark-results.json');
|
||||
|
||||
if (!fs.existsSync(resultsPath)) {
|
||||
console.error('benchmark-results.json not found');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const vitestResults = JSON.parse(fs.readFileSync(resultsPath, 'utf8'));
|
||||
|
||||
// Convert to github-action-benchmark format
|
||||
const formattedResults = [];
|
||||
|
||||
// Vitest benchmark JSON reporter format
|
||||
if (vitestResults.files) {
|
||||
for (const file of vitestResults.files) {
|
||||
const suiteName = path.basename(file.filepath, '.bench.ts');
|
||||
|
||||
// Process each suite in the file
|
||||
if (file.groups) {
|
||||
for (const group of file.groups) {
|
||||
for (const benchmark of group.benchmarks || []) {
|
||||
if (benchmark.result) {
|
||||
formattedResults.push({
|
||||
name: `${suiteName} - ${benchmark.name}`,
|
||||
unit: 'ms',
|
||||
value: benchmark.result.mean || 0,
|
||||
range: (benchmark.result.max - benchmark.result.min) || 0,
|
||||
extra: `${benchmark.result.hz?.toFixed(0) || 0} ops/sec`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (Array.isArray(vitestResults)) {
|
||||
// Alternative format handling
|
||||
for (const result of vitestResults) {
|
||||
if (result.name && result.result) {
|
||||
formattedResults.push({
|
||||
name: result.name,
|
||||
unit: 'ms',
|
||||
value: result.result.mean || 0,
|
||||
range: (result.result.max - result.result.min) || 0,
|
||||
extra: `${result.result.hz?.toFixed(0) || 0} ops/sec`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write formatted results
|
||||
const outputPath = path.join(process.cwd(), 'benchmark-results-formatted.json');
|
||||
fs.writeFileSync(outputPath, JSON.stringify(formattedResults, null, 2));
|
||||
|
||||
// Also create a summary for PR comments
|
||||
const summary = {
|
||||
timestamp: new Date().toISOString(),
|
||||
benchmarks: formattedResults.map(b => ({
|
||||
name: b.name,
|
||||
time: `${b.value.toFixed(3)}ms`,
|
||||
opsPerSec: b.extra,
|
||||
range: `±${(b.range / 2).toFixed(3)}ms`
|
||||
}))
|
||||
};
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(process.cwd(), 'benchmark-summary.json'),
|
||||
JSON.stringify(summary, null, 2)
|
||||
);
|
||||
|
||||
console.log(`Formatted ${formattedResults.length} benchmark results`);
|
||||
}
|
||||
|
||||
// Run if called directly
|
||||
if (require.main === module) {
|
||||
formatBenchmarkResults();
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Generates a stub benchmark-results.json file when benchmarks fail to produce output.
|
||||
* This ensures the CI pipeline doesn't fail due to missing files.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const stubResults = {
|
||||
timestamp: new Date().toISOString(),
|
||||
files: [
|
||||
{
|
||||
filepath: 'tests/benchmarks/stub.bench.ts',
|
||||
groups: [
|
||||
{
|
||||
name: 'Stub Benchmarks',
|
||||
benchmarks: [
|
||||
{
|
||||
name: 'stub-benchmark',
|
||||
result: {
|
||||
mean: 0.001,
|
||||
min: 0.001,
|
||||
max: 0.001,
|
||||
hz: 1000,
|
||||
p75: 0.001,
|
||||
p99: 0.001,
|
||||
p995: 0.001,
|
||||
p999: 0.001,
|
||||
rme: 0,
|
||||
samples: 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const outputPath = path.join(process.cwd(), 'benchmark-results.json');
|
||||
fs.writeFileSync(outputPath, JSON.stringify(stubResults, null, 2));
|
||||
console.log(`Generated stub benchmark results at ${outputPath}`);
|
||||
@@ -1,172 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { spawn } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const benchmarkResults = {
|
||||
timestamp: new Date().toISOString(),
|
||||
files: []
|
||||
};
|
||||
|
||||
// Function to strip ANSI color codes
|
||||
function stripAnsi(str) {
|
||||
return str.replace(/\x1b\[[0-9;]*m/g, '');
|
||||
}
|
||||
|
||||
// Run vitest bench command with no color output for easier parsing
|
||||
const vitest = spawn('npx', ['vitest', 'bench', '--run', '--config', 'vitest.config.benchmark.ts', '--no-color'], {
|
||||
stdio: ['inherit', 'pipe', 'pipe'],
|
||||
shell: true,
|
||||
env: { ...process.env, NO_COLOR: '1', FORCE_COLOR: '0' }
|
||||
});
|
||||
|
||||
let output = '';
|
||||
let currentFile = null;
|
||||
let currentSuite = null;
|
||||
|
||||
vitest.stdout.on('data', (data) => {
|
||||
const text = stripAnsi(data.toString());
|
||||
output += text;
|
||||
process.stdout.write(data); // Write original with colors
|
||||
|
||||
// Parse the output to extract benchmark results
|
||||
const lines = text.split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
// Detect test file - match with or without checkmark
|
||||
const fileMatch = line.match(/[✓ ]\s+(tests\/benchmarks\/[^>]+\.bench\.ts)/);
|
||||
if (fileMatch) {
|
||||
console.log(`\n[Parser] Found file: ${fileMatch[1]}`);
|
||||
currentFile = {
|
||||
filepath: fileMatch[1],
|
||||
groups: []
|
||||
};
|
||||
benchmarkResults.files.push(currentFile);
|
||||
currentSuite = null;
|
||||
}
|
||||
|
||||
// Detect suite name
|
||||
const suiteMatch = line.match(/^\s+·\s+(.+?)\s+[\d,]+\.\d+\s+/);
|
||||
if (suiteMatch && currentFile) {
|
||||
const suiteName = suiteMatch[1].trim();
|
||||
|
||||
// Check if this is part of the previous line's suite description
|
||||
const lastLineMatch = lines[lines.indexOf(line) - 1]?.match(/>\s+(.+?)(?:\s+\d+ms)?$/);
|
||||
if (lastLineMatch) {
|
||||
currentSuite = {
|
||||
name: lastLineMatch[1].trim(),
|
||||
benchmarks: []
|
||||
};
|
||||
currentFile.groups.push(currentSuite);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse benchmark result line - the format is: name hz min max mean p75 p99 p995 p999 rme samples
|
||||
const benchMatch = line.match(/^\s*[·•]\s+(.+?)\s+([\d,]+\.\d+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+±([\d.]+)%\s+([\d,]+)/);
|
||||
if (benchMatch && currentFile) {
|
||||
const [, name, hz, min, max, mean, p75, p99, p995, p999, rme, samples] = benchMatch;
|
||||
console.log(`[Parser] Found benchmark: ${name.trim()}`);
|
||||
|
||||
|
||||
const benchmark = {
|
||||
name: name.trim(),
|
||||
result: {
|
||||
hz: parseFloat(hz.replace(/,/g, '')),
|
||||
min: parseFloat(min),
|
||||
max: parseFloat(max),
|
||||
mean: parseFloat(mean),
|
||||
p75: parseFloat(p75),
|
||||
p99: parseFloat(p99),
|
||||
p995: parseFloat(p995),
|
||||
p999: parseFloat(p999),
|
||||
rme: parseFloat(rme),
|
||||
samples: parseInt(samples.replace(/,/g, ''))
|
||||
}
|
||||
};
|
||||
|
||||
// Add to current suite or create a default one
|
||||
if (!currentSuite) {
|
||||
currentSuite = {
|
||||
name: 'Default',
|
||||
benchmarks: []
|
||||
};
|
||||
currentFile.groups.push(currentSuite);
|
||||
}
|
||||
|
||||
currentSuite.benchmarks.push(benchmark);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
vitest.stderr.on('data', (data) => {
|
||||
process.stderr.write(data);
|
||||
});
|
||||
|
||||
vitest.on('close', (code) => {
|
||||
if (code !== 0) {
|
||||
console.error(`Benchmark process exited with code ${code}`);
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
// Clean up empty files/groups
|
||||
benchmarkResults.files = benchmarkResults.files.filter(file =>
|
||||
file.groups.length > 0 && file.groups.some(group => group.benchmarks.length > 0)
|
||||
);
|
||||
|
||||
// Write results
|
||||
const outputPath = path.join(process.cwd(), 'benchmark-results.json');
|
||||
fs.writeFileSync(outputPath, JSON.stringify(benchmarkResults, null, 2));
|
||||
console.log(`\nBenchmark results written to ${outputPath}`);
|
||||
console.log(`Total files processed: ${benchmarkResults.files.length}`);
|
||||
|
||||
// Validate that we captured results
|
||||
let totalBenchmarks = 0;
|
||||
for (const file of benchmarkResults.files) {
|
||||
for (const group of file.groups) {
|
||||
totalBenchmarks += group.benchmarks.length;
|
||||
}
|
||||
}
|
||||
|
||||
if (totalBenchmarks === 0) {
|
||||
console.warn('No benchmark results were captured! Generating stub results...');
|
||||
|
||||
// Generate stub results to prevent CI failure
|
||||
const stubResults = {
|
||||
timestamp: new Date().toISOString(),
|
||||
files: [
|
||||
{
|
||||
filepath: 'tests/benchmarks/sample.bench.ts',
|
||||
groups: [
|
||||
{
|
||||
name: 'Sample Benchmarks',
|
||||
benchmarks: [
|
||||
{
|
||||
name: 'array sorting - small',
|
||||
result: {
|
||||
mean: 0.0136,
|
||||
min: 0.0124,
|
||||
max: 0.3220,
|
||||
hz: 73341.27,
|
||||
p75: 0.0133,
|
||||
p99: 0.0213,
|
||||
p995: 0.0307,
|
||||
p999: 0.1062,
|
||||
rme: 0.51,
|
||||
samples: 36671
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
fs.writeFileSync(outputPath, JSON.stringify(stubResults, null, 2));
|
||||
console.log('Stub results generated to prevent CI failure');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Total benchmarks captured: ${totalBenchmarks}`);
|
||||
});
|
||||
@@ -1,121 +0,0 @@
|
||||
const { writeFileSync } = require('fs');
|
||||
const { resolve } = require('path');
|
||||
|
||||
class BenchmarkJsonReporter {
|
||||
constructor() {
|
||||
this.results = [];
|
||||
console.log('[BenchmarkJsonReporter] Initialized');
|
||||
}
|
||||
|
||||
onInit(ctx) {
|
||||
console.log('[BenchmarkJsonReporter] onInit called');
|
||||
}
|
||||
|
||||
onCollected(files) {
|
||||
console.log('[BenchmarkJsonReporter] onCollected called with', files ? files.length : 0, 'files');
|
||||
}
|
||||
|
||||
onTaskUpdate(tasks) {
|
||||
console.log('[BenchmarkJsonReporter] onTaskUpdate called');
|
||||
}
|
||||
|
||||
onBenchmarkResult(file, benchmark) {
|
||||
console.log('[BenchmarkJsonReporter] onBenchmarkResult called for', benchmark.name);
|
||||
}
|
||||
|
||||
onFinished(files, errors) {
|
||||
console.log('[BenchmarkJsonReporter] onFinished called with', files ? files.length : 0, 'files');
|
||||
|
||||
const results = {
|
||||
timestamp: new Date().toISOString(),
|
||||
files: []
|
||||
};
|
||||
|
||||
try {
|
||||
for (const file of files || []) {
|
||||
if (!file) continue;
|
||||
|
||||
const fileResult = {
|
||||
filepath: file.filepath || file.name || 'unknown',
|
||||
groups: []
|
||||
};
|
||||
|
||||
// Handle both file.tasks and file.benchmarks
|
||||
const tasks = file.tasks || file.benchmarks || [];
|
||||
|
||||
// Process tasks/benchmarks
|
||||
for (const task of tasks) {
|
||||
if (task.type === 'suite' && task.tasks) {
|
||||
// This is a suite containing benchmarks
|
||||
const group = {
|
||||
name: task.name,
|
||||
benchmarks: []
|
||||
};
|
||||
|
||||
for (const benchmark of task.tasks) {
|
||||
if (benchmark.result?.benchmark) {
|
||||
group.benchmarks.push({
|
||||
name: benchmark.name,
|
||||
result: {
|
||||
mean: benchmark.result.benchmark.mean,
|
||||
min: benchmark.result.benchmark.min,
|
||||
max: benchmark.result.benchmark.max,
|
||||
hz: benchmark.result.benchmark.hz,
|
||||
p75: benchmark.result.benchmark.p75,
|
||||
p99: benchmark.result.benchmark.p99,
|
||||
p995: benchmark.result.benchmark.p995,
|
||||
p999: benchmark.result.benchmark.p999,
|
||||
rme: benchmark.result.benchmark.rme,
|
||||
samples: benchmark.result.benchmark.samples
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (group.benchmarks.length > 0) {
|
||||
fileResult.groups.push(group);
|
||||
}
|
||||
} else if (task.result?.benchmark) {
|
||||
// This is a direct benchmark (not in a suite)
|
||||
if (!fileResult.groups.length) {
|
||||
fileResult.groups.push({
|
||||
name: 'Default',
|
||||
benchmarks: []
|
||||
});
|
||||
}
|
||||
|
||||
fileResult.groups[0].benchmarks.push({
|
||||
name: task.name,
|
||||
result: {
|
||||
mean: task.result.benchmark.mean,
|
||||
min: task.result.benchmark.min,
|
||||
max: task.result.benchmark.max,
|
||||
hz: task.result.benchmark.hz,
|
||||
p75: task.result.benchmark.p75,
|
||||
p99: task.result.benchmark.p99,
|
||||
p995: task.result.benchmark.p995,
|
||||
p999: task.result.benchmark.p999,
|
||||
rme: task.result.benchmark.rme,
|
||||
samples: task.result.benchmark.samples
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (fileResult.groups.length > 0) {
|
||||
results.files.push(fileResult);
|
||||
}
|
||||
}
|
||||
|
||||
// Write results
|
||||
const outputPath = resolve(process.cwd(), 'benchmark-results.json');
|
||||
writeFileSync(outputPath, JSON.stringify(results, null, 2));
|
||||
console.log(`[BenchmarkJsonReporter] Benchmark results written to ${outputPath}`);
|
||||
console.log(`[BenchmarkJsonReporter] Total files processed: ${results.files.length}`);
|
||||
} catch (error) {
|
||||
console.error('[BenchmarkJsonReporter] Error writing results:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BenchmarkJsonReporter;
|
||||
@@ -1,100 +0,0 @@
|
||||
import type { Task, TaskResult, BenchmarkResult } from 'vitest';
|
||||
import { writeFileSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
|
||||
interface BenchmarkJsonResult {
|
||||
timestamp: string;
|
||||
files: Array<{
|
||||
filepath: string;
|
||||
groups: Array<{
|
||||
name: string;
|
||||
benchmarks: Array<{
|
||||
name: string;
|
||||
result: {
|
||||
mean: number;
|
||||
min: number;
|
||||
max: number;
|
||||
hz: number;
|
||||
p75: number;
|
||||
p99: number;
|
||||
p995: number;
|
||||
p999: number;
|
||||
rme: number;
|
||||
samples: number;
|
||||
};
|
||||
}>;
|
||||
}>;
|
||||
}>;
|
||||
}
|
||||
|
||||
export class BenchmarkJsonReporter {
|
||||
private results: BenchmarkJsonResult = {
|
||||
timestamp: new Date().toISOString(),
|
||||
files: []
|
||||
};
|
||||
|
||||
onInit() {
|
||||
console.log('[BenchmarkJsonReporter] Initialized');
|
||||
}
|
||||
|
||||
onFinished(files?: Task[]) {
|
||||
console.log('[BenchmarkJsonReporter] onFinished called');
|
||||
|
||||
if (!files) {
|
||||
console.log('[BenchmarkJsonReporter] No files provided');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const file of files) {
|
||||
const fileResult = {
|
||||
filepath: file.filepath || 'unknown',
|
||||
groups: [] as any[]
|
||||
};
|
||||
|
||||
this.processTask(file, fileResult);
|
||||
|
||||
if (fileResult.groups.length > 0) {
|
||||
this.results.files.push(fileResult);
|
||||
}
|
||||
}
|
||||
|
||||
// Write results
|
||||
const outputPath = resolve(process.cwd(), 'benchmark-results.json');
|
||||
writeFileSync(outputPath, JSON.stringify(this.results, null, 2));
|
||||
console.log(`[BenchmarkJsonReporter] Results written to ${outputPath}`);
|
||||
}
|
||||
|
||||
private processTask(task: Task, fileResult: any) {
|
||||
if (task.type === 'suite' && task.tasks) {
|
||||
const group = {
|
||||
name: task.name,
|
||||
benchmarks: [] as any[]
|
||||
};
|
||||
|
||||
for (const benchmark of task.tasks) {
|
||||
const result = benchmark.result as TaskResult & { benchmark?: BenchmarkResult };
|
||||
if (result?.benchmark) {
|
||||
group.benchmarks.push({
|
||||
name: benchmark.name,
|
||||
result: {
|
||||
mean: result.benchmark.mean || 0,
|
||||
min: result.benchmark.min || 0,
|
||||
max: result.benchmark.max || 0,
|
||||
hz: result.benchmark.hz || 0,
|
||||
p75: result.benchmark.p75 || 0,
|
||||
p99: result.benchmark.p99 || 0,
|
||||
p995: result.benchmark.p995 || 0,
|
||||
p999: result.benchmark.p999 || 0,
|
||||
rme: result.benchmark.rme || 0,
|
||||
samples: result.benchmark.samples?.length || 0
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (group.benchmarks.length > 0) {
|
||||
fileResult.groups.push(group);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,8 @@ const DEFAULT_CONFIG: Required<Omit<DocumentationGeneratorConfig, 'baseUrl' | 't
|
||||
*/
|
||||
export class DocumentationGenerator {
|
||||
private client: OpenAI;
|
||||
private baseUrl: string;
|
||||
private apiKey: string;
|
||||
private model: string;
|
||||
private maxTokens: number;
|
||||
private timeout: number;
|
||||
@@ -85,6 +87,8 @@ export class DocumentationGenerator {
|
||||
constructor(config: DocumentationGeneratorConfig) {
|
||||
const fullConfig = { ...DEFAULT_CONFIG, ...config };
|
||||
|
||||
this.baseUrl = config.baseUrl;
|
||||
this.apiKey = fullConfig.apiKey;
|
||||
this.client = new OpenAI({
|
||||
baseURL: config.baseUrl,
|
||||
apiKey: fullConfig.apiKey,
|
||||
@@ -103,21 +107,10 @@ export class DocumentationGenerator {
|
||||
try {
|
||||
const prompt = this.buildPrompt(input);
|
||||
|
||||
const completion = await this.client.chat.completions.create({
|
||||
model: this.model,
|
||||
max_completion_tokens: this.maxTokens,
|
||||
...(this.temperature !== undefined ? { temperature: this.temperature } : {}),
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: this.getSystemPrompt(),
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: prompt,
|
||||
},
|
||||
],
|
||||
});
|
||||
const completion = await this.chatCompletion([
|
||||
{ role: 'system', content: this.getSystemPrompt() },
|
||||
{ role: 'user', content: prompt },
|
||||
], this.maxTokens);
|
||||
|
||||
const content = completion.choices[0]?.message?.content;
|
||||
if (!content) {
|
||||
@@ -246,20 +239,23 @@ Guidelines:
|
||||
* Extract JSON from LLM response (handles markdown code blocks)
|
||||
*/
|
||||
private extractJson(content: string): string {
|
||||
// Strip <think>...</think> blocks from thinking models (e.g., Qwen3-Thinking)
|
||||
const stripped = content.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
|
||||
|
||||
// Try to extract from markdown code block
|
||||
const jsonBlockMatch = content.match(/```(?:json)?\s*([\s\S]*?)```/);
|
||||
const jsonBlockMatch = stripped.match(/```(?:json)?\s*([\s\S]*?)```/);
|
||||
if (jsonBlockMatch) {
|
||||
return jsonBlockMatch[1].trim();
|
||||
}
|
||||
|
||||
// Try to find JSON object directly
|
||||
const jsonMatch = content.match(/\{[\s\S]*\}/);
|
||||
const jsonMatch = stripped.match(/\{[\s\S]*\}/);
|
||||
if (jsonMatch) {
|
||||
return jsonMatch[0];
|
||||
}
|
||||
|
||||
// Return as-is if no extraction needed
|
||||
return content.trim();
|
||||
return stripped;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -323,16 +319,9 @@ Guidelines:
|
||||
*/
|
||||
async testConnection(): Promise<{ success: boolean; message: string }> {
|
||||
try {
|
||||
const completion = await this.client.chat.completions.create({
|
||||
model: this.model,
|
||||
max_completion_tokens: 200,
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: 'Hello',
|
||||
},
|
||||
],
|
||||
});
|
||||
const completion = await this.chatCompletion([
|
||||
{ role: 'user', content: 'Hello' },
|
||||
], 200);
|
||||
|
||||
if (completion.choices[0]?.message?.content) {
|
||||
return { success: true, message: `Connected to ${this.model}` };
|
||||
@@ -345,6 +334,44 @@ Guidelines:
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a chat completion request with chat_template_kwargs support for vLLM thinking models
|
||||
*/
|
||||
private async chatCompletion(
|
||||
messages: Array<{ role: string; content: string }>,
|
||||
maxTokens: number
|
||||
): Promise<{ choices: Array<{ message: { content: string | null } }> }> {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(this.apiKey !== 'not-needed' ? { Authorization: `Bearer ${this.apiKey}` } : {}),
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: this.model,
|
||||
messages,
|
||||
max_completion_tokens: maxTokens,
|
||||
...(this.temperature !== undefined ? { temperature: this.temperature } : {}),
|
||||
chat_template_kwargs: { enable_thinking: false },
|
||||
}),
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
throw new Error(`${response.status} ${text}`);
|
||||
}
|
||||
|
||||
return (await response.json()) as { choices: Array<{ message: { content: string | null } }> };
|
||||
} finally {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
}
|
||||
|
||||
private sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
@@ -34,6 +34,11 @@ export class NodeRepository {
|
||||
* Supports both core and community nodes via optional community fields
|
||||
*/
|
||||
saveNode(node: ParsedNode & Partial<CommunityNodeFields>): void {
|
||||
// Preserve existing npm_readme and ai_documentation_summary on upsert
|
||||
const existing = this.db.prepare(
|
||||
'SELECT npm_readme, ai_documentation_summary, ai_summary_generated_at FROM nodes WHERE node_type = ?'
|
||||
).get(node.nodeType) as { npm_readme?: string; ai_documentation_summary?: string; ai_summary_generated_at?: string } | undefined;
|
||||
|
||||
const stmt = this.db.prepare(`
|
||||
INSERT OR REPLACE INTO nodes (
|
||||
node_type, package_name, display_name, description,
|
||||
@@ -43,8 +48,9 @@ export class NodeRepository {
|
||||
properties_schema, operations, credentials_required,
|
||||
outputs, output_names,
|
||||
is_community, is_verified, author_name, author_github_url,
|
||||
npm_package_name, npm_version, npm_downloads, community_fetched_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
npm_package_name, npm_version, npm_downloads, community_fetched_at,
|
||||
npm_readme, ai_documentation_summary, ai_summary_generated_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
stmt.run(
|
||||
@@ -76,7 +82,11 @@ export class NodeRepository {
|
||||
node.npmPackageName || null,
|
||||
node.npmVersion || null,
|
||||
node.npmDownloads || 0,
|
||||
node.communityFetchedAt || null
|
||||
node.communityFetchedAt || null,
|
||||
// Preserve existing docs data on upsert
|
||||
existing?.npm_readme || null,
|
||||
existing?.ai_documentation_summary || null,
|
||||
existing?.ai_summary_generated_at || null
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -253,6 +253,22 @@ export class SingleSessionHTTPServer {
|
||||
// This ensures compatibility with all MCP clients and proxies
|
||||
return Boolean(sessionId && sessionId.length > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a request body is a JSON-RPC notification (or batch of only notifications).
|
||||
* Per JSON-RPC 2.0 §4.1, a notification is a request without an "id" member.
|
||||
* Note: `!('id' in msg)` is strict — messages with `id: null` are treated as
|
||||
* requests, not notifications. This is spec-compliant.
|
||||
*/
|
||||
private isJsonRpcNotification(body: unknown): boolean {
|
||||
if (!body || typeof body !== 'object') return false;
|
||||
const isSingleNotification = (msg: any): boolean =>
|
||||
msg && typeof msg.method === 'string' && !('id' in msg);
|
||||
if (Array.isArray(body)) {
|
||||
return body.length > 0 && body.every(isSingleNotification);
|
||||
}
|
||||
return isSingleNotification(body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize error information for client responses
|
||||
@@ -614,6 +630,22 @@ export class SingleSessionHTTPServer {
|
||||
logger.info('handleRequest: Reusing existing transport for session', { sessionId });
|
||||
transport = this.transports[sessionId];
|
||||
|
||||
// TOCTOU guard: session may have been removed between the check above and here
|
||||
if (!transport) {
|
||||
if (this.isJsonRpcNotification(req.body)) {
|
||||
logger.info('handleRequest: Session removed during lookup, accepting notification', { sessionId });
|
||||
res.status(202).end();
|
||||
return;
|
||||
}
|
||||
logger.warn('handleRequest: Session removed between check and use (TOCTOU)', { sessionId });
|
||||
res.status(400).json({
|
||||
jsonrpc: '2.0',
|
||||
error: { code: -32000, message: 'Bad Request: Session not found or expired' },
|
||||
id: req.body?.id || null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// In multi-tenant shared mode, update instance context if provided
|
||||
const isMultiTenantEnabled = process.env.ENABLE_MULTI_TENANT === 'true';
|
||||
const sessionStrategy = process.env.MULTI_TENANT_SESSION_STRATEGY || 'instance';
|
||||
@@ -627,23 +659,33 @@ export class SingleSessionHTTPServer {
|
||||
this.updateSessionAccess(sessionId);
|
||||
|
||||
} else {
|
||||
// Invalid request - no session ID and not an initialize request
|
||||
// Notifications are fire-and-forget; returning 400 triggers reconnection storms (#654)
|
||||
if (this.isJsonRpcNotification(req.body)) {
|
||||
logger.info('handleRequest: Accepting notification for stale/missing session', {
|
||||
method: req.body?.method,
|
||||
sessionId: sessionId || 'none',
|
||||
});
|
||||
res.status(202).end();
|
||||
return;
|
||||
}
|
||||
|
||||
// Only return 400 for actual requests that need a valid session
|
||||
const errorDetails = {
|
||||
hasSessionId: !!sessionId,
|
||||
isInitialize: isInitialize,
|
||||
sessionIdValid: sessionId ? this.isValidSessionId(sessionId) : false,
|
||||
sessionExists: sessionId ? !!this.transports[sessionId] : false
|
||||
};
|
||||
|
||||
|
||||
logger.warn('handleRequest: Invalid request - no session ID and not initialize', errorDetails);
|
||||
|
||||
|
||||
let errorMessage = 'Bad Request: No valid session ID provided and not an initialize request';
|
||||
if (sessionId && !this.isValidSessionId(sessionId)) {
|
||||
errorMessage = 'Bad Request: Invalid session ID format';
|
||||
} else if (sessionId && !this.transports[sessionId]) {
|
||||
errorMessage = 'Bad Request: Session not found or expired';
|
||||
}
|
||||
|
||||
|
||||
res.status(400).json({
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
WebhookRequest,
|
||||
McpToolResponse,
|
||||
ExecutionFilterOptions,
|
||||
ExecutionMode
|
||||
ExecutionMode,
|
||||
} from '../types/n8n-api';
|
||||
import type { TriggerType, TestWorkflowInput } from '../triggers/types';
|
||||
import {
|
||||
@@ -383,6 +383,7 @@ const createWorkflowSchema = z.object({
|
||||
executionTimeout: z.number().optional(),
|
||||
errorWorkflow: z.string().optional(),
|
||||
}).optional(),
|
||||
projectId: z.string().optional(),
|
||||
});
|
||||
|
||||
const updateWorkflowSchema = z.object({
|
||||
@@ -1974,7 +1975,7 @@ export async function handleDiagnostic(request: any, context?: InstanceContext):
|
||||
|
||||
// Check which tools are available
|
||||
const documentationTools = 7; // Base documentation tools (after v2.26.0 consolidation)
|
||||
const managementTools = apiConfigured ? 13 : 0; // Management tools requiring API (includes n8n_deploy_template)
|
||||
const managementTools = apiConfigured ? 14 : 0; // Management tools requiring API (includes n8n_manage_datatable)
|
||||
const totalTools = documentationTools + managementTools;
|
||||
|
||||
// Check npm version
|
||||
@@ -2688,3 +2689,260 @@ export async function handleTriggerWebhookWorkflow(args: unknown, context?: Inst
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Data Table Handlers
|
||||
// ========================================================================
|
||||
|
||||
// Shared Zod schemas for data table operations
|
||||
const dataTableFilterConditionSchema = z.object({
|
||||
columnName: z.string().min(1),
|
||||
condition: z.enum(['eq', 'neq', 'like', 'ilike', 'gt', 'gte', 'lt', 'lte']),
|
||||
value: z.any(),
|
||||
});
|
||||
|
||||
const dataTableFilterSchema = z.object({
|
||||
type: z.enum(['and', 'or']).optional().default('and'),
|
||||
filters: z.array(dataTableFilterConditionSchema).min(1, 'At least one filter condition is required'),
|
||||
});
|
||||
|
||||
// Shared base schema for actions requiring a tableId
|
||||
const tableIdSchema = z.object({
|
||||
tableId: z.string().min(1, 'tableId is required'),
|
||||
});
|
||||
|
||||
// Per-action Zod schemas
|
||||
const createTableSchema = z.object({
|
||||
name: z.string().min(1, 'Table name cannot be empty'),
|
||||
columns: z.array(z.object({
|
||||
name: z.string().min(1, 'Column name cannot be empty'),
|
||||
type: z.enum(['string', 'number', 'boolean', 'date']).optional(),
|
||||
})).optional(),
|
||||
});
|
||||
|
||||
const listTablesSchema = z.object({
|
||||
limit: z.number().min(1).max(100).optional(),
|
||||
cursor: z.string().optional(),
|
||||
});
|
||||
|
||||
const updateTableSchema = tableIdSchema.extend({
|
||||
name: z.string().min(1, 'New table name cannot be empty'),
|
||||
});
|
||||
|
||||
// MCP transports may serialize JSON objects/arrays as strings.
|
||||
// Parse them back, but return the original value on failure so Zod reports a proper type error.
|
||||
function tryParseJson(val: unknown): unknown {
|
||||
if (typeof val !== 'string') return val;
|
||||
try { return JSON.parse(val); } catch { return val; }
|
||||
}
|
||||
|
||||
const coerceJsonArray = z.preprocess(tryParseJson, z.array(z.record(z.unknown())));
|
||||
const coerceJsonObject = z.preprocess(tryParseJson, z.record(z.unknown()));
|
||||
const coerceJsonFilter = z.preprocess(tryParseJson, dataTableFilterSchema);
|
||||
|
||||
const getRowsSchema = tableIdSchema.extend({
|
||||
limit: z.number().min(1).max(100).optional(),
|
||||
cursor: z.string().optional(),
|
||||
filter: z.union([coerceJsonFilter, z.string()]).optional(),
|
||||
sortBy: z.string().optional(),
|
||||
search: z.string().optional(),
|
||||
});
|
||||
|
||||
const insertRowsSchema = tableIdSchema.extend({
|
||||
data: coerceJsonArray.pipe(z.array(z.record(z.unknown())).min(1, 'At least one row is required')),
|
||||
returnType: z.enum(['count', 'id', 'all']).optional(),
|
||||
});
|
||||
|
||||
// Shared schema for update/upsert (identical structure)
|
||||
const mutateRowsSchema = tableIdSchema.extend({
|
||||
filter: coerceJsonFilter,
|
||||
data: coerceJsonObject,
|
||||
returnData: z.boolean().optional(),
|
||||
dryRun: z.boolean().optional(),
|
||||
});
|
||||
|
||||
const deleteRowsSchema = tableIdSchema.extend({
|
||||
filter: coerceJsonFilter,
|
||||
returnData: z.boolean().optional(),
|
||||
dryRun: z.boolean().optional(),
|
||||
});
|
||||
|
||||
function handleDataTableError(error: unknown): McpToolResponse {
|
||||
if (error instanceof z.ZodError) {
|
||||
return { success: false, error: 'Invalid input', details: { errors: error.errors } };
|
||||
}
|
||||
if (error instanceof N8nApiError) {
|
||||
return {
|
||||
success: false,
|
||||
error: getUserFriendlyErrorMessage(error),
|
||||
code: error.code,
|
||||
details: error.details as Record<string, unknown> | undefined,
|
||||
};
|
||||
}
|
||||
return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred' };
|
||||
}
|
||||
|
||||
export async function handleCreateTable(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
|
||||
try {
|
||||
const client = ensureApiConfigured(context);
|
||||
const input = createTableSchema.parse(args);
|
||||
const dataTable = await client.createDataTable(input);
|
||||
if (!dataTable || !dataTable.id) {
|
||||
return { success: false, error: 'Data table creation failed: n8n API returned an empty or invalid response' };
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
data: { id: dataTable.id, name: dataTable.name },
|
||||
message: `Data table "${dataTable.name}" created with ID: ${dataTable.id}`,
|
||||
};
|
||||
} catch (error) {
|
||||
return handleDataTableError(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleListTables(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
|
||||
try {
|
||||
const client = ensureApiConfigured(context);
|
||||
const input = listTablesSchema.parse(args || {});
|
||||
const result = await client.listDataTables(input);
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
tables: result.data,
|
||||
count: result.data.length,
|
||||
nextCursor: result.nextCursor || undefined,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return handleDataTableError(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleGetTable(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
|
||||
try {
|
||||
const client = ensureApiConfigured(context);
|
||||
const { tableId } = tableIdSchema.parse(args);
|
||||
const dataTable = await client.getDataTable(tableId);
|
||||
return { success: true, data: dataTable };
|
||||
} catch (error) {
|
||||
return handleDataTableError(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleUpdateTable(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
|
||||
try {
|
||||
const client = ensureApiConfigured(context);
|
||||
const { tableId, name } = updateTableSchema.parse(args);
|
||||
const dataTable = await client.updateDataTable(tableId, { name });
|
||||
const rawArgs = args as Record<string, unknown>;
|
||||
const hasColumns = rawArgs && typeof rawArgs === 'object' && 'columns' in rawArgs;
|
||||
return {
|
||||
success: true,
|
||||
data: dataTable,
|
||||
message: `Data table renamed to "${dataTable.name}"` +
|
||||
(hasColumns ? '. Note: columns parameter was ignored — table schema is immutable after creation via the public API' : ''),
|
||||
};
|
||||
} catch (error) {
|
||||
return handleDataTableError(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleDeleteTable(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
|
||||
try {
|
||||
const client = ensureApiConfigured(context);
|
||||
const { tableId } = tableIdSchema.parse(args);
|
||||
await client.deleteDataTable(tableId);
|
||||
return { success: true, message: `Data table ${tableId} deleted successfully` };
|
||||
} catch (error) {
|
||||
return handleDataTableError(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleGetRows(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
|
||||
try {
|
||||
const client = ensureApiConfigured(context);
|
||||
const { tableId, filter, sortBy, ...params } = getRowsSchema.parse(args);
|
||||
const queryParams: Record<string, unknown> = { ...params };
|
||||
if (filter) {
|
||||
queryParams.filter = typeof filter === 'string' ? filter : JSON.stringify(filter);
|
||||
}
|
||||
if (sortBy) {
|
||||
queryParams.sortBy = sortBy;
|
||||
}
|
||||
const result = await client.getDataTableRows(tableId, queryParams as any);
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
rows: result.data,
|
||||
count: result.data.length,
|
||||
nextCursor: result.nextCursor || undefined,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return handleDataTableError(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleInsertRows(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
|
||||
try {
|
||||
const client = ensureApiConfigured(context);
|
||||
const { tableId, ...params } = insertRowsSchema.parse(args);
|
||||
const result = await client.insertDataTableRows(tableId, params);
|
||||
return {
|
||||
success: true,
|
||||
data: result,
|
||||
message: `Rows inserted into data table ${tableId}`,
|
||||
};
|
||||
} catch (error) {
|
||||
return handleDataTableError(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleUpdateRows(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
|
||||
try {
|
||||
const client = ensureApiConfigured(context);
|
||||
const { tableId, ...params } = mutateRowsSchema.parse(args);
|
||||
const result = await client.updateDataTableRows(tableId, params);
|
||||
return {
|
||||
success: true,
|
||||
data: result,
|
||||
message: params.dryRun ? 'Dry run: rows matched (no changes applied)' : 'Rows updated successfully',
|
||||
};
|
||||
} catch (error) {
|
||||
return handleDataTableError(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleUpsertRows(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
|
||||
try {
|
||||
const client = ensureApiConfigured(context);
|
||||
const { tableId, ...params } = mutateRowsSchema.parse(args);
|
||||
const result = await client.upsertDataTableRow(tableId, params);
|
||||
return {
|
||||
success: true,
|
||||
data: result,
|
||||
message: params.dryRun ? 'Dry run: upsert previewed (no changes applied)' : 'Row upserted successfully',
|
||||
};
|
||||
} catch (error) {
|
||||
return handleDataTableError(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleDeleteRows(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
|
||||
try {
|
||||
const client = ensureApiConfigured(context);
|
||||
const { tableId, filter, ...params } = deleteRowsSchema.parse(args);
|
||||
const queryParams = {
|
||||
filter: JSON.stringify(filter),
|
||||
...params,
|
||||
};
|
||||
const result = await client.deleteDataTableRows(tableId, queryParams as any);
|
||||
return {
|
||||
success: true,
|
||||
data: result,
|
||||
message: params.dryRun ? 'Dry run: rows matched for deletion (no changes applied)' : 'Rows deleted successfully',
|
||||
};
|
||||
} catch (error) {
|
||||
return handleDataTableError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,8 @@ const workflowDiffSchema = z.object({
|
||||
settings: z.any().optional(),
|
||||
name: z.string().optional(),
|
||||
tag: z.string().optional(),
|
||||
// Transfer operation
|
||||
destinationProjectId: z.string().min(1).optional(),
|
||||
// Aliases: LLMs often use "id" instead of "nodeId" — accept both
|
||||
id: z.string().optional(),
|
||||
}).transform((op) => {
|
||||
@@ -370,6 +372,26 @@ export async function handleUpdatePartialWorkflow(
|
||||
}
|
||||
}
|
||||
|
||||
// Handle project transfer if requested (before activation so workflow is in target project first)
|
||||
let transferMessage = '';
|
||||
if (diffResult.transferToProjectId) {
|
||||
try {
|
||||
await client.transferWorkflow(input.id, diffResult.transferToProjectId);
|
||||
transferMessage = ` Workflow transferred to project ${diffResult.transferToProjectId}.`;
|
||||
} catch (transferError) {
|
||||
logger.error('Failed to transfer workflow to project', transferError);
|
||||
return {
|
||||
success: false,
|
||||
saved: true,
|
||||
error: 'Workflow updated successfully but project transfer failed',
|
||||
details: {
|
||||
workflowUpdated: true,
|
||||
transferError: transferError instanceof Error ? transferError.message : 'Unknown error'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Handle activation/deactivation if requested
|
||||
let finalWorkflow = updatedWorkflow;
|
||||
let activationMessage = '';
|
||||
@@ -454,7 +476,7 @@ export async function handleUpdatePartialWorkflow(
|
||||
nodeCount: finalWorkflow.nodes?.length || 0,
|
||||
operationsApplied: diffResult.operationsApplied
|
||||
},
|
||||
message: `Workflow "${finalWorkflow.name}" updated successfully. Applied ${diffResult.operationsApplied} operations.${activationMessage} Use n8n_get_workflow with mode 'structure' to verify current state.`,
|
||||
message: `Workflow "${finalWorkflow.name}" updated successfully. Applied ${diffResult.operationsApplied} operations.${transferMessage}${activationMessage} Use n8n_get_workflow with mode 'structure' to verify current state.`,
|
||||
details: {
|
||||
applied: diffResult.applied,
|
||||
failed: diffResult.failed,
|
||||
@@ -559,6 +581,8 @@ function inferIntentFromOperations(operations: any[]): string {
|
||||
return 'Activate workflow';
|
||||
case 'deactivateWorkflow':
|
||||
return 'Deactivate workflow';
|
||||
case 'transferWorkflow':
|
||||
return `Transfer workflow to project ${op.destinationProjectId || ''}`.trim();
|
||||
default:
|
||||
return `Workflow ${op.type}`;
|
||||
}
|
||||
|
||||
@@ -1029,6 +1029,11 @@ export class N8NDocumentationMCPServer {
|
||||
? { valid: true, errors: [] }
|
||||
: { valid: false, errors: [{ field: 'action', message: 'action is required' }] };
|
||||
break;
|
||||
case 'n8n_manage_datatable':
|
||||
validationResult = args.action
|
||||
? { valid: true, errors: [] }
|
||||
: { valid: false, errors: [{ field: 'action', message: 'action is required' }] };
|
||||
break;
|
||||
case 'n8n_deploy_template':
|
||||
// Requires templateId parameter
|
||||
validationResult = args.templateId !== undefined
|
||||
@@ -1496,6 +1501,26 @@ export class N8NDocumentationMCPServer {
|
||||
if (!this.repository) throw new Error('Repository not initialized');
|
||||
return n8nHandlers.handleDeployTemplate(args, this.templateService, this.repository, this.instanceContext);
|
||||
|
||||
case 'n8n_manage_datatable': {
|
||||
this.validateToolParams(name, args, ['action']);
|
||||
const dtAction = args.action;
|
||||
// Each handler validates its own inputs via Zod schemas
|
||||
switch (dtAction) {
|
||||
case 'createTable': return n8nHandlers.handleCreateTable(args, this.instanceContext);
|
||||
case 'listTables': return n8nHandlers.handleListTables(args, this.instanceContext);
|
||||
case 'getTable': return n8nHandlers.handleGetTable(args, this.instanceContext);
|
||||
case 'updateTable': return n8nHandlers.handleUpdateTable(args, this.instanceContext);
|
||||
case 'deleteTable': return n8nHandlers.handleDeleteTable(args, this.instanceContext);
|
||||
case 'getRows': return n8nHandlers.handleGetRows(args, this.instanceContext);
|
||||
case 'insertRows': return n8nHandlers.handleInsertRows(args, this.instanceContext);
|
||||
case 'updateRows': return n8nHandlers.handleUpdateRows(args, this.instanceContext);
|
||||
case 'upsertRows': return n8nHandlers.handleUpsertRows(args, this.instanceContext);
|
||||
case 'deleteRows': return n8nHandlers.handleDeleteRows(args, this.instanceContext);
|
||||
default:
|
||||
throw new Error(`Unknown action: ${dtAction}. Valid actions: createTable, listTables, getTable, updateTable, deleteTable, getRows, insertRows, updateRows, upsertRows, deleteRows`);
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${name}`);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@ import {
|
||||
n8nTestWorkflowDoc,
|
||||
n8nExecutionsDoc,
|
||||
n8nWorkflowVersionsDoc,
|
||||
n8nDeployTemplateDoc
|
||||
n8nDeployTemplateDoc,
|
||||
n8nManageDatatableDoc
|
||||
} from './workflow_management';
|
||||
|
||||
// Combine all tool documentations into a single object
|
||||
@@ -60,7 +61,8 @@ export const toolsDocumentation: Record<string, ToolDocumentation> = {
|
||||
n8n_test_workflow: n8nTestWorkflowDoc,
|
||||
n8n_executions: n8nExecutionsDoc,
|
||||
n8n_workflow_versions: n8nWorkflowVersionsDoc,
|
||||
n8n_deploy_template: n8nDeployTemplateDoc
|
||||
n8n_deploy_template: n8nDeployTemplateDoc,
|
||||
n8n_manage_datatable: n8nManageDatatableDoc
|
||||
};
|
||||
|
||||
// Re-export types
|
||||
|
||||
@@ -10,3 +10,4 @@ export { n8nTestWorkflowDoc } from './n8n-test-workflow';
|
||||
export { n8nExecutionsDoc } from './n8n-executions';
|
||||
export { n8nWorkflowVersionsDoc } from './n8n-workflow-versions';
|
||||
export { n8nDeployTemplateDoc } from './n8n-deploy-template';
|
||||
export { n8nManageDatatableDoc } from './n8n-manage-datatable';
|
||||
|
||||
107
src/mcp/tool-docs/workflow_management/n8n-manage-datatable.ts
Normal file
107
src/mcp/tool-docs/workflow_management/n8n-manage-datatable.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { ToolDocumentation } from '../types';
|
||||
|
||||
export const n8nManageDatatableDoc: ToolDocumentation = {
|
||||
name: 'n8n_manage_datatable',
|
||||
category: 'workflow_management',
|
||||
essentials: {
|
||||
description: 'Manage n8n data tables and rows. Unified tool for table CRUD and row operations with filtering, pagination, and dry-run support.',
|
||||
keyParameters: ['action', 'tableId', 'name', 'data', 'filter'],
|
||||
example: 'n8n_manage_datatable({action: "createTable", name: "Contacts", columns: [{name: "email", type: "string"}]})',
|
||||
performance: 'Fast (100-500ms)',
|
||||
tips: [
|
||||
'Table actions: createTable, listTables, getTable, updateTable (rename only), deleteTable',
|
||||
'Row actions: getRows, insertRows, updateRows, upsertRows, deleteRows',
|
||||
'Use dryRun: true to preview update/upsert/delete before applying',
|
||||
'Filter supports: eq, neq, like, ilike, gt, gte, lt, lte conditions',
|
||||
'Use returnData: true to get affected rows back from update/upsert/delete',
|
||||
'Requires N8N_API_URL and N8N_API_KEY configured'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
description: `**Table Actions:**
|
||||
- **createTable**: Create a new data table with optional typed columns
|
||||
- **listTables**: List all data tables (paginated)
|
||||
- **getTable**: Get table details and column definitions by ID
|
||||
- **updateTable**: Rename an existing table (name only — column modifications not supported via API)
|
||||
- **deleteTable**: Permanently delete a table and all its rows
|
||||
|
||||
**Row Actions:**
|
||||
- **getRows**: List rows with filtering, sorting, search, and pagination
|
||||
- **insertRows**: Insert one or more rows (bulk)
|
||||
- **updateRows**: Update rows matching a filter condition
|
||||
- **upsertRows**: Update matching row or insert if none match
|
||||
- **deleteRows**: Delete rows matching a filter condition (filter required)
|
||||
|
||||
**Filter System:** Used in getRows, updateRows, upsertRows, deleteRows
|
||||
- Combine conditions with "and" (default) or "or"
|
||||
- Conditions: eq, neq, like, ilike, gt, gte, lt, lte
|
||||
- Example: {type: "and", filters: [{columnName: "status", condition: "eq", value: "active"}]}
|
||||
|
||||
**Dry Run:** updateRows, upsertRows, and deleteRows support dryRun: true to preview changes without applying them.`,
|
||||
parameters: {
|
||||
action: { type: 'string', required: true, description: 'Operation to perform' },
|
||||
tableId: { type: 'string', required: false, description: 'Data table ID (required for all except createTable and listTables)' },
|
||||
name: { type: 'string', required: false, description: 'For createTable/updateTable: table name' },
|
||||
columns: { type: 'array', required: false, description: 'For createTable: column definitions [{name, type?}]. Types: string, number, boolean, date' },
|
||||
data: { type: 'array|object', required: false, description: 'For insertRows: array of row objects. For updateRows/upsertRows: object with column values' },
|
||||
filter: { type: 'object', required: false, description: 'Filter: {type?: "and"|"or", filters: [{columnName, condition, value}]}' },
|
||||
limit: { type: 'number', required: false, description: 'For listTables/getRows: max results (1-100)' },
|
||||
cursor: { type: 'string', required: false, description: 'For listTables/getRows: pagination cursor' },
|
||||
sortBy: { type: 'string', required: false, description: 'For getRows: "columnName:asc" or "columnName:desc"' },
|
||||
search: { type: 'string', required: false, description: 'For getRows: full-text search across string columns' },
|
||||
returnType: { type: 'string', required: false, description: 'For insertRows: "count" (default), "id", or "all"' },
|
||||
returnData: { type: 'boolean', required: false, description: 'For updateRows/upsertRows/deleteRows: return affected rows (default: false)' },
|
||||
dryRun: { type: 'boolean', required: false, description: 'For updateRows/upsertRows/deleteRows: preview without applying (default: false)' },
|
||||
},
|
||||
returns: `Depends on action:
|
||||
- createTable: {id, name}
|
||||
- listTables: {tables, count, nextCursor?}
|
||||
- getTable: Full table object with columns
|
||||
- updateTable: Updated table object
|
||||
- deleteTable: Success message
|
||||
- getRows: {rows, count, nextCursor?}
|
||||
- insertRows: Depends on returnType (count/ids/rows)
|
||||
- updateRows: Update result with optional rows
|
||||
- upsertRows: Upsert result with action type
|
||||
- deleteRows: Delete result with optional rows`,
|
||||
examples: [
|
||||
'// Create a table\nn8n_manage_datatable({action: "createTable", name: "Contacts", columns: [{name: "email", type: "string"}, {name: "score", type: "number"}]})',
|
||||
'// List all tables\nn8n_manage_datatable({action: "listTables"})',
|
||||
'// Get table details\nn8n_manage_datatable({action: "getTable", tableId: "dt-123"})',
|
||||
'// Rename a table\nn8n_manage_datatable({action: "updateTable", tableId: "dt-123", name: "New Name"})',
|
||||
'// Delete a table\nn8n_manage_datatable({action: "deleteTable", tableId: "dt-123"})',
|
||||
'// Get rows with filter\nn8n_manage_datatable({action: "getRows", tableId: "dt-123", filter: {filters: [{columnName: "status", condition: "eq", value: "active"}]}, limit: 50})',
|
||||
'// Search rows\nn8n_manage_datatable({action: "getRows", tableId: "dt-123", search: "john", sortBy: "name:asc"})',
|
||||
'// Insert rows\nn8n_manage_datatable({action: "insertRows", tableId: "dt-123", data: [{email: "a@b.com", score: 10}], returnType: "all"})',
|
||||
'// Update rows (dry run)\nn8n_manage_datatable({action: "updateRows", tableId: "dt-123", filter: {filters: [{columnName: "score", condition: "lt", value: 5}]}, data: {status: "inactive"}, dryRun: true})',
|
||||
'// Upsert a row\nn8n_manage_datatable({action: "upsertRows", tableId: "dt-123", filter: {filters: [{columnName: "email", condition: "eq", value: "a@b.com"}]}, data: {score: 15}, returnData: true})',
|
||||
'// Delete rows\nn8n_manage_datatable({action: "deleteRows", tableId: "dt-123", filter: {filters: [{columnName: "status", condition: "eq", value: "deleted"}]}})',
|
||||
],
|
||||
useCases: [
|
||||
'Persist structured workflow data across executions',
|
||||
'Store and query lookup tables for workflow logic',
|
||||
'Bulk insert records from external data sources',
|
||||
'Conditionally update records matching criteria',
|
||||
'Upsert to maintain unique records by key column',
|
||||
'Clean up old or invalid rows with filtered delete',
|
||||
'Preview changes with dryRun before modifying data',
|
||||
],
|
||||
performance: 'Table operations: 50-300ms. Row operations: 100-500ms depending on data size and filters.',
|
||||
bestPractices: [
|
||||
'Define column types upfront for schema consistency',
|
||||
'Use dryRun: true before bulk updates/deletes to verify filter correctness',
|
||||
'Use returnType: "count" (default) for insertRows to minimize response size',
|
||||
'Use filter with specific conditions to avoid unintended bulk operations',
|
||||
'Use cursor-based pagination for large result sets',
|
||||
'Use sortBy for deterministic row ordering',
|
||||
],
|
||||
pitfalls: [
|
||||
'deleteTable permanently deletes all rows — cannot be undone',
|
||||
'deleteRows requires a filter — cannot delete all rows without one',
|
||||
'Column types cannot be changed after table creation via API',
|
||||
'updateTable can only rename the table (no column modifications via public API)',
|
||||
'projectId cannot be set via the public API — use the n8n UI',
|
||||
],
|
||||
relatedTools: ['n8n_create_workflow', 'n8n_list_workflows', 'n8n_health_check'],
|
||||
},
|
||||
};
|
||||
@@ -4,7 +4,7 @@ export const n8nUpdatePartialWorkflowDoc: ToolDocumentation = {
|
||||
name: 'n8n_update_partial_workflow',
|
||||
category: 'workflow_management',
|
||||
essentials: {
|
||||
description: 'Update workflow incrementally with diff operations. Types: addNode, removeNode, updateNode, moveNode, enable/disableNode, addConnection, removeConnection, rewireConnection, cleanStaleConnections, replaceConnections, updateSettings, updateName, add/removeTag, activateWorkflow, deactivateWorkflow. Supports smart parameters (branch, case) for multi-output nodes. Full support for AI connections (ai_languageModel, ai_tool, ai_memory, ai_embedding, ai_vectorStore, ai_document, ai_textSplitter, ai_outputParser).',
|
||||
description: 'Update workflow incrementally with diff operations. Types: addNode, removeNode, updateNode, moveNode, enable/disableNode, addConnection, removeConnection, rewireConnection, cleanStaleConnections, replaceConnections, updateSettings, updateName, add/removeTag, activateWorkflow, deactivateWorkflow, transferWorkflow. Supports smart parameters (branch, case) for multi-output nodes. Full support for AI connections (ai_languageModel, ai_tool, ai_memory, ai_embedding, ai_vectorStore, ai_document, ai_textSplitter, ai_outputParser).',
|
||||
keyParameters: ['id', 'operations', 'continueOnError'],
|
||||
example: 'n8n_update_partial_workflow({id: "wf_123", operations: [{type: "rewireConnection", source: "IF", from: "Old", to: "New", branch: "true"}]})',
|
||||
performance: 'Fast (50-200ms)',
|
||||
@@ -22,7 +22,8 @@ export const n8nUpdatePartialWorkflowDoc: ToolDocumentation = {
|
||||
'Batch AI component connections for atomic updates',
|
||||
'Auto-sanitization: ALL nodes auto-fixed during updates (operator structures, missing metadata)',
|
||||
'Node renames automatically update all connection references - no manual connection operations needed',
|
||||
'Activate/deactivate workflows: Use activateWorkflow/deactivateWorkflow operations (requires activatable triggers like webhook/schedule)'
|
||||
'Activate/deactivate workflows: Use activateWorkflow/deactivateWorkflow operations (requires activatable triggers like webhook/schedule)',
|
||||
'Transfer workflows between projects: Use transferWorkflow with destinationProjectId (enterprise feature)'
|
||||
]
|
||||
},
|
||||
full: {
|
||||
@@ -55,6 +56,9 @@ export const n8nUpdatePartialWorkflowDoc: ToolDocumentation = {
|
||||
- **activateWorkflow**: Activate the workflow to enable automatic execution via triggers
|
||||
- **deactivateWorkflow**: Deactivate the workflow to prevent automatic execution
|
||||
|
||||
### Project Management Operations (1 type):
|
||||
- **transferWorkflow**: Transfer the workflow to a different project. Requires \`destinationProjectId\`. Enterprise/cloud feature.
|
||||
|
||||
## Smart Parameters for Multi-Output Nodes
|
||||
|
||||
For **IF nodes**, use semantic 'branch' parameter instead of technical sourceIndex:
|
||||
@@ -345,7 +349,10 @@ n8n_update_partial_workflow({
|
||||
'// Migrate from deprecated continueOnFail to onError\nn8n_update_partial_workflow({id: "rm2", operations: [{type: "updateNode", nodeName: "HTTP Request", updates: {continueOnFail: null, onError: "continueErrorOutput"}}]})',
|
||||
'// Remove nested property\nn8n_update_partial_workflow({id: "rm3", operations: [{type: "updateNode", nodeName: "API Request", updates: {"parameters.authentication": null}}]})',
|
||||
'// Remove multiple properties\nn8n_update_partial_workflow({id: "rm4", operations: [{type: "updateNode", nodeName: "Data Processor", updates: {continueOnFail: null, alwaysOutputData: null, "parameters.legacy_option": null}}]})',
|
||||
'// Remove entire array property\nn8n_update_partial_workflow({id: "rm5", operations: [{type: "updateNode", nodeName: "HTTP Request", updates: {"parameters.headers": null}}]})'
|
||||
'// Remove entire array property\nn8n_update_partial_workflow({id: "rm5", operations: [{type: "updateNode", nodeName: "HTTP Request", updates: {"parameters.headers": null}}]})',
|
||||
'\n// ============ PROJECT TRANSFER EXAMPLES ============',
|
||||
'// Transfer workflow to a different project\nn8n_update_partial_workflow({id: "tf1", operations: [{type: "transferWorkflow", destinationProjectId: "project-abc-123"}]})',
|
||||
'// Transfer and activate in one call\nn8n_update_partial_workflow({id: "tf2", operations: [{type: "transferWorkflow", destinationProjectId: "project-abc-123"}, {type: "activateWorkflow"}]})'
|
||||
],
|
||||
useCases: [
|
||||
'Rewire connections when replacing nodes',
|
||||
@@ -363,7 +370,8 @@ n8n_update_partial_workflow({
|
||||
'Add fallback language models to AI Agents',
|
||||
'Configure Vector Store retrieval systems',
|
||||
'Swap language models in existing AI workflows',
|
||||
'Batch-update AI tool connections'
|
||||
'Batch-update AI tool connections',
|
||||
'Transfer workflows between team projects (enterprise)'
|
||||
],
|
||||
performance: 'Very fast - typically 50-200ms. Much faster than full updates as only changes are processed.',
|
||||
bestPractices: [
|
||||
|
||||
@@ -63,6 +63,10 @@ export const n8nManagementTools: ToolDefinition[] = [
|
||||
executionTimeout: { type: 'number' },
|
||||
errorWorkflow: { type: 'string' }
|
||||
}
|
||||
},
|
||||
projectId: {
|
||||
type: 'string',
|
||||
description: 'Optional project ID to create the workflow in (enterprise feature)'
|
||||
}
|
||||
},
|
||||
required: ['name', 'nodes', 'connections']
|
||||
@@ -143,7 +147,7 @@ export const n8nManagementTools: ToolDefinition[] = [
|
||||
},
|
||||
{
|
||||
name: 'n8n_update_partial_workflow',
|
||||
description: `Update workflow incrementally with diff operations. Types: addNode, removeNode, updateNode, moveNode, enable/disableNode, addConnection, removeConnection, updateSettings, updateName, add/removeTag. See tools_documentation("n8n_update_partial_workflow", "full") for details.`,
|
||||
description: `Update workflow incrementally with diff operations. Types: addNode, removeNode, updateNode, moveNode, enable/disableNode, addConnection, removeConnection, updateSettings, updateName, add/removeTag, activate/deactivateWorkflow, transferWorkflow. See tools_documentation("n8n_update_partial_workflow", "full") for details.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
additionalProperties: true, // Allow any extra properties Claude Desktop might add
|
||||
@@ -602,5 +606,52 @@ export const n8nManagementTools: ToolDefinition[] = [
|
||||
destructiveHint: false,
|
||||
openWorldHint: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'n8n_manage_datatable',
|
||||
description: `Manage n8n data tables and rows. Actions: createTable, listTables, getTable, updateTable, deleteTable, getRows, insertRows, updateRows, upsertRows, deleteRows.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
action: {
|
||||
type: 'string',
|
||||
enum: ['createTable', 'listTables', 'getTable', 'updateTable', 'deleteTable', 'getRows', 'insertRows', 'updateRows', 'upsertRows', 'deleteRows'],
|
||||
description: 'Operation to perform',
|
||||
},
|
||||
tableId: { type: 'string', description: 'Data table ID (required for all actions except createTable and listTables)' },
|
||||
name: { type: 'string', description: 'For createTable: table name. For updateTable: new name (rename only — schema is immutable after creation)' },
|
||||
columns: {
|
||||
type: 'array',
|
||||
description: 'For createTable only: column definitions (schema is immutable after creation via public API)',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
type: { type: 'string', enum: ['string', 'number', 'boolean', 'date'] },
|
||||
},
|
||||
required: ['name'],
|
||||
},
|
||||
},
|
||||
data: { description: 'For insertRows: array of row objects. For updateRows/upsertRows: object with column values.' },
|
||||
filter: {
|
||||
type: 'object',
|
||||
description: 'For getRows/updateRows/upsertRows/deleteRows: {type?: "and"|"or", filters: [{columnName, condition, value}]}',
|
||||
},
|
||||
limit: { type: 'number', description: 'For listTables/getRows: max results (1-100)' },
|
||||
cursor: { type: 'string', description: 'For listTables/getRows: pagination cursor' },
|
||||
sortBy: { type: 'string', description: 'For getRows: "columnName:asc" or "columnName:desc"' },
|
||||
search: { type: 'string', description: 'For getRows: text search across string columns' },
|
||||
returnType: { type: 'string', enum: ['count', 'id', 'all'], description: 'For insertRows: what to return (default: count)' },
|
||||
returnData: { type: 'boolean', description: 'For updateRows/upsertRows/deleteRows: return affected rows (default: false)' },
|
||||
dryRun: { type: 'boolean', description: 'For updateRows/upsertRows/deleteRows: preview without applying (default: false)' },
|
||||
},
|
||||
required: ['action'],
|
||||
},
|
||||
annotations: {
|
||||
title: 'Manage Data Tables',
|
||||
readOnlyHint: false,
|
||||
destructiveHint: true,
|
||||
openWorldHint: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
* Fetch community nodes from n8n Strapi API and npm registry.
|
||||
*
|
||||
* Usage:
|
||||
* npm run fetch:community # Full rebuild (verified + top 100 npm)
|
||||
* npm run fetch:community # Upsert all (preserves READMEs and AI summaries)
|
||||
* npm run fetch:community:verified # Verified nodes only (fast)
|
||||
* npm run fetch:community:update # Incremental update (skip existing)
|
||||
*
|
||||
* Options:
|
||||
* --verified-only Only fetch verified nodes from Strapi API
|
||||
* --update Skip nodes that already exist in database
|
||||
* --rebuild Delete all community nodes first (wipes READMEs/AI summaries!)
|
||||
* --npm-limit=N Maximum number of npm packages to fetch (default: 100)
|
||||
* --staging Use staging Strapi API instead of production
|
||||
*/
|
||||
@@ -22,6 +23,7 @@ import { createDatabaseAdapter } from '../database/database-adapter';
|
||||
interface CliOptions {
|
||||
verifiedOnly: boolean;
|
||||
update: boolean;
|
||||
rebuild: boolean;
|
||||
npmLimit: number;
|
||||
staging: boolean;
|
||||
}
|
||||
@@ -32,6 +34,7 @@ function parseArgs(): CliOptions {
|
||||
const options: CliOptions = {
|
||||
verifiedOnly: false,
|
||||
update: false,
|
||||
rebuild: false,
|
||||
npmLimit: 100,
|
||||
staging: false,
|
||||
};
|
||||
@@ -41,6 +44,8 @@ function parseArgs(): CliOptions {
|
||||
options.verifiedOnly = true;
|
||||
} else if (arg === '--update') {
|
||||
options.update = true;
|
||||
} else if (arg === '--rebuild') {
|
||||
options.rebuild = true;
|
||||
} else if (arg === '--staging') {
|
||||
options.staging = true;
|
||||
} else if (arg.startsWith('--npm-limit=')) {
|
||||
@@ -73,7 +78,7 @@ async function main(): Promise<void> {
|
||||
|
||||
// Print options
|
||||
console.log('Options:');
|
||||
console.log(` - Mode: ${cliOptions.update ? 'Update (incremental)' : 'Rebuild'}`);
|
||||
console.log(` - Mode: ${cliOptions.rebuild ? 'Rebuild (clean slate)' : cliOptions.update ? 'Update (skip existing)' : 'Upsert (preserves docs)'}`);
|
||||
console.log(` - Verified only: ${cliOptions.verifiedOnly ? 'Yes' : 'No'}`);
|
||||
if (!cliOptions.verifiedOnly) {
|
||||
console.log(` - npm package limit: ${cliOptions.npmLimit}`);
|
||||
@@ -92,9 +97,10 @@ async function main(): Promise<void> {
|
||||
const environment = cliOptions.staging ? 'staging' : 'production';
|
||||
const service = new CommunityNodeService(repository, environment);
|
||||
|
||||
// If not updating, delete existing community nodes
|
||||
if (!cliOptions.update) {
|
||||
console.log('\nClearing existing community nodes...');
|
||||
// Only delete existing community nodes when --rebuild is explicitly requested
|
||||
if (cliOptions.rebuild) {
|
||||
console.log('\nClearing existing community nodes (--rebuild)...');
|
||||
console.log(' WARNING: This wipes READMEs and AI summaries!');
|
||||
const deleted = service.deleteCommunityNodes();
|
||||
console.log(` Deleted ${deleted} existing community nodes`);
|
||||
}
|
||||
|
||||
@@ -124,7 +124,15 @@ async function rebuild() {
|
||||
}
|
||||
|
||||
console.log(`💾 Save completed: ${saved} nodes saved successfully`);
|
||||
|
||||
|
||||
// Rebuild FTS5 index to guarantee consistency.
|
||||
// The content-synced FTS5 table (content=nodes) can accumulate stale rowid
|
||||
// references when rows are deleted and re-inserted during a rebuild cycle.
|
||||
// An explicit rebuild re-indexes all current rows from the nodes table.
|
||||
console.log('\n🔍 Rebuilding FTS5 search index...');
|
||||
db.prepare("INSERT INTO nodes_fts(nodes_fts) VALUES('rebuild')").run();
|
||||
console.log('✅ FTS5 index rebuilt successfully');
|
||||
|
||||
// Validation check
|
||||
console.log('\n🔍 Running validation checks...');
|
||||
try {
|
||||
|
||||
@@ -22,6 +22,15 @@ import {
|
||||
SourceControlStatus,
|
||||
SourceControlPullResult,
|
||||
SourceControlPushResult,
|
||||
DataTable,
|
||||
DataTableColumn,
|
||||
DataTableListParams,
|
||||
DataTableRow,
|
||||
DataTableRowListParams,
|
||||
DataTableInsertRowsParams,
|
||||
DataTableUpdateRowsParams,
|
||||
DataTableUpsertRowParams,
|
||||
DataTableDeleteRowsParams,
|
||||
} from '../types/n8n-api';
|
||||
import { handleN8nApiError, logN8nError } from '../utils/n8n-errors';
|
||||
import { cleanWorkflowForCreate, cleanWorkflowForUpdate } from './n8n-validation';
|
||||
@@ -252,6 +261,14 @@ export class N8nApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
async transferWorkflow(id: string, destinationProjectId: string): Promise<void> {
|
||||
try {
|
||||
await this.client.put(`/workflows/${id}/transfer`, { destinationProjectId });
|
||||
} catch (error) {
|
||||
throw handleN8nApiError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async activateWorkflow(id: string): Promise<Workflow> {
|
||||
try {
|
||||
const response = await this.client.post(`/workflows/${id}/activate`, {});
|
||||
@@ -574,6 +591,114 @@ export class N8nApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
async createDataTable(params: { name: string; columns?: DataTableColumn[] }): Promise<DataTable> {
|
||||
try {
|
||||
const response = await this.client.post('/data-tables', params);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw handleN8nApiError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async listDataTables(params: DataTableListParams = {}): Promise<{ data: DataTable[]; nextCursor?: string | null }> {
|
||||
try {
|
||||
const response = await this.client.get('/data-tables', { params });
|
||||
return this.validateListResponse<DataTable>(response.data, 'data-tables');
|
||||
} catch (error) {
|
||||
throw handleN8nApiError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getDataTable(id: string): Promise<DataTable> {
|
||||
try {
|
||||
const response = await this.client.get(`/data-tables/${id}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw handleN8nApiError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async updateDataTable(id: string, params: { name: string }): Promise<DataTable> {
|
||||
try {
|
||||
const response = await this.client.patch(`/data-tables/${id}`, params);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw handleN8nApiError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteDataTable(id: string): Promise<void> {
|
||||
try {
|
||||
await this.client.delete(`/data-tables/${id}`);
|
||||
} catch (error) {
|
||||
throw handleN8nApiError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getDataTableRows(id: string, params: DataTableRowListParams = {}): Promise<{ data: DataTableRow[]; nextCursor?: string | null }> {
|
||||
try {
|
||||
const response = await this.client.get(`/data-tables/${id}/rows`, {
|
||||
params,
|
||||
paramsSerializer: (p) => this.serializeDataTableParams(p),
|
||||
});
|
||||
return this.validateListResponse<DataTableRow>(response.data, 'data-table-rows');
|
||||
} catch (error) {
|
||||
throw handleN8nApiError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async insertDataTableRows(id: string, params: DataTableInsertRowsParams): Promise<any> {
|
||||
try {
|
||||
const response = await this.client.post(`/data-tables/${id}/rows`, params);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw handleN8nApiError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async updateDataTableRows(id: string, params: DataTableUpdateRowsParams): Promise<any> {
|
||||
try {
|
||||
const response = await this.client.patch(`/data-tables/${id}/rows/update`, params);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw handleN8nApiError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async upsertDataTableRow(id: string, params: DataTableUpsertRowParams): Promise<any> {
|
||||
try {
|
||||
const response = await this.client.post(`/data-tables/${id}/rows/upsert`, params);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw handleN8nApiError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteDataTableRows(id: string, params: DataTableDeleteRowsParams): Promise<any> {
|
||||
try {
|
||||
const response = await this.client.delete(`/data-tables/${id}/rows/delete`, {
|
||||
params,
|
||||
paramsSerializer: (p) => this.serializeDataTableParams(p),
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw handleN8nApiError(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes data table query params with explicit encodeURIComponent.
|
||||
* Axios's default serializer doesn't encode some reserved chars that n8n rejects.
|
||||
*/
|
||||
private serializeDataTableParams(params: Record<string, any>): string {
|
||||
const parts: string[] = [];
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value === undefined || value === null) continue;
|
||||
parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
|
||||
}
|
||||
return parts.join('&');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and normalizes n8n API list responses.
|
||||
* Handles both modern format {data: [], nextCursor?: string} and legacy array format.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import crypto from 'crypto';
|
||||
import { z } from 'zod';
|
||||
import { WorkflowNode, WorkflowConnection, Workflow } from '../types/n8n-api';
|
||||
import { isTriggerNode, isActivatableTrigger } from '../utils/node-type-utils';
|
||||
@@ -87,6 +88,22 @@ export function validateWorkflowSettings(settings: unknown): z.infer<typeof work
|
||||
return workflowSettingsSchema.parse(settings);
|
||||
}
|
||||
|
||||
const WEBHOOK_NODE_TYPES = new Set([
|
||||
'n8n-nodes-base.webhook',
|
||||
'n8n-nodes-base.webhookTrigger',
|
||||
'n8n-nodes-base.formTrigger',
|
||||
'@n8n/n8n-nodes-langchain.chatTrigger',
|
||||
]);
|
||||
|
||||
function ensureWebhookIds(nodes?: WorkflowNode[]): void {
|
||||
if (!nodes) return;
|
||||
for (const node of nodes) {
|
||||
if (WEBHOOK_NODE_TYPES.has(node.type) && !node.webhookId) {
|
||||
node.webhookId = crypto.randomUUID();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean workflow data for API operations
|
||||
export function cleanWorkflowForCreate(workflow: Partial<Workflow>): Partial<Workflow> {
|
||||
const {
|
||||
@@ -109,6 +126,8 @@ export function cleanWorkflowForCreate(workflow: Partial<Workflow>): Partial<Wor
|
||||
cleanedWorkflow.settings = defaultWorkflowSettings;
|
||||
}
|
||||
|
||||
ensureWebhookIds(cleanedWorkflow.nodes);
|
||||
|
||||
return cleanedWorkflow;
|
||||
}
|
||||
|
||||
@@ -194,6 +213,8 @@ export function cleanWorkflowForUpdate(workflow: Workflow): Partial<Workflow> {
|
||||
cleanedWorkflow.settings = { executionOrder: 'v1' as const };
|
||||
}
|
||||
|
||||
ensureWebhookIds(cleanedWorkflow.nodes);
|
||||
|
||||
return cleanedWorkflow;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,8 @@ import {
|
||||
ActivateWorkflowOperation,
|
||||
DeactivateWorkflowOperation,
|
||||
CleanStaleConnectionsOperation,
|
||||
ReplaceConnectionsOperation
|
||||
ReplaceConnectionsOperation,
|
||||
TransferWorkflowOperation
|
||||
} from '../types/workflow-diff';
|
||||
import { Workflow, WorkflowNode, WorkflowConnection } from '../types/n8n-api';
|
||||
import { Logger } from '../utils/logger';
|
||||
@@ -54,6 +55,8 @@ export class WorkflowDiffEngine {
|
||||
// Track tag operations for dedicated API calls
|
||||
private tagsToAdd: string[] = [];
|
||||
private tagsToRemove: string[] = [];
|
||||
// Track transfer operation for dedicated API call
|
||||
private transferToProjectId: string | undefined;
|
||||
|
||||
/**
|
||||
* Apply diff operations to a workflow
|
||||
@@ -70,6 +73,7 @@ export class WorkflowDiffEngine {
|
||||
this.removedNodeNames.clear();
|
||||
this.tagsToAdd = [];
|
||||
this.tagsToRemove = [];
|
||||
this.transferToProjectId = undefined;
|
||||
|
||||
// Clone workflow to avoid modifying original
|
||||
const workflowCopy = JSON.parse(JSON.stringify(workflow));
|
||||
@@ -141,6 +145,12 @@ export class WorkflowDiffEngine {
|
||||
};
|
||||
}
|
||||
|
||||
// Extract and clean up activation flags (same as atomic mode)
|
||||
const shouldActivate = (workflowCopy as any)._shouldActivate === true;
|
||||
const shouldDeactivate = (workflowCopy as any)._shouldDeactivate === true;
|
||||
delete (workflowCopy as any)._shouldActivate;
|
||||
delete (workflowCopy as any)._shouldDeactivate;
|
||||
|
||||
const success = appliedIndices.length > 0;
|
||||
return {
|
||||
success,
|
||||
@@ -151,8 +161,11 @@ export class WorkflowDiffEngine {
|
||||
warnings: this.warnings.length > 0 ? this.warnings : undefined,
|
||||
applied: appliedIndices,
|
||||
failed: failedIndices,
|
||||
shouldActivate: shouldActivate || undefined,
|
||||
shouldDeactivate: shouldDeactivate || undefined,
|
||||
tagsToAdd: this.tagsToAdd.length > 0 ? this.tagsToAdd : undefined,
|
||||
tagsToRemove: this.tagsToRemove.length > 0 ? this.tagsToRemove : undefined
|
||||
tagsToRemove: this.tagsToRemove.length > 0 ? this.tagsToRemove : undefined,
|
||||
transferToProjectId: this.transferToProjectId || undefined
|
||||
};
|
||||
} else {
|
||||
// Atomic mode: all operations must succeed
|
||||
@@ -256,7 +269,8 @@ export class WorkflowDiffEngine {
|
||||
shouldActivate: shouldActivate || undefined,
|
||||
shouldDeactivate: shouldDeactivate || undefined,
|
||||
tagsToAdd: this.tagsToAdd.length > 0 ? this.tagsToAdd : undefined,
|
||||
tagsToRemove: this.tagsToRemove.length > 0 ? this.tagsToRemove : undefined
|
||||
tagsToRemove: this.tagsToRemove.length > 0 ? this.tagsToRemove : undefined,
|
||||
transferToProjectId: this.transferToProjectId || undefined
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -298,6 +312,8 @@ export class WorkflowDiffEngine {
|
||||
case 'addTag':
|
||||
case 'removeTag':
|
||||
return null; // These are always valid
|
||||
case 'transferWorkflow':
|
||||
return this.validateTransferWorkflow(workflow, operation as TransferWorkflowOperation);
|
||||
case 'activateWorkflow':
|
||||
return this.validateActivateWorkflow(workflow, operation);
|
||||
case 'deactivateWorkflow':
|
||||
@@ -367,6 +383,9 @@ export class WorkflowDiffEngine {
|
||||
case 'replaceConnections':
|
||||
this.applyReplaceConnections(workflow, operation);
|
||||
break;
|
||||
case 'transferWorkflow':
|
||||
this.applyTransferWorkflow(workflow, operation as TransferWorkflowOperation);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -975,6 +994,18 @@ export class WorkflowDiffEngine {
|
||||
(workflow as any)._shouldDeactivate = true;
|
||||
}
|
||||
|
||||
// Transfer operation — uses dedicated API call (PUT /workflows/{id}/transfer)
|
||||
private validateTransferWorkflow(_workflow: Workflow, operation: TransferWorkflowOperation): string | null {
|
||||
if (!operation.destinationProjectId) {
|
||||
return 'transferWorkflow requires a non-empty destinationProjectId string';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private applyTransferWorkflow(_workflow: Workflow, operation: TransferWorkflowOperation): void {
|
||||
this.transferToProjectId = operation.destinationProjectId;
|
||||
}
|
||||
|
||||
// Connection cleanup operation validators
|
||||
private validateCleanStaleConnections(workflow: Workflow, operation: CleanStaleConnectionsOperation): string | null {
|
||||
// This operation is always valid - it just cleans up what it finds
|
||||
@@ -1128,9 +1159,10 @@ export class WorkflowDiffEngine {
|
||||
const connection = connectionsAtIndex[connIndex];
|
||||
// Check if target node was renamed
|
||||
if (renames.has(connection.node)) {
|
||||
const oldTargetName = connection.node;
|
||||
const newTargetName = renames.get(connection.node)!;
|
||||
connection.node = newTargetName;
|
||||
logger.debug(`Updated connection: ${sourceName}[${outputType}][${outputIndex}][${connIndex}].node: "${connection.node}" → "${newTargetName}"`);
|
||||
logger.debug(`Updated connection: ${sourceName}[${outputType}][${outputIndex}][${connIndex}].node: "${oldTargetName}" → "${newTargetName}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,4 +454,82 @@ export interface ErrorSuggestion {
|
||||
title: string;
|
||||
description: string;
|
||||
confidence: 'high' | 'medium' | 'low';
|
||||
}
|
||||
|
||||
// Data Table types
|
||||
export interface DataTableColumn {
|
||||
name: string;
|
||||
type?: 'string' | 'number' | 'boolean' | 'date';
|
||||
}
|
||||
|
||||
export interface DataTableColumnResponse {
|
||||
id: string;
|
||||
name: string;
|
||||
type: 'string' | 'number' | 'boolean' | 'date';
|
||||
index: number;
|
||||
}
|
||||
|
||||
export interface DataTable {
|
||||
id: string;
|
||||
name: string;
|
||||
columns?: DataTableColumnResponse[];
|
||||
projectId?: string;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface DataTableRow {
|
||||
id?: number;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
[columnName: string]: unknown;
|
||||
}
|
||||
|
||||
export interface DataTableFilterCondition {
|
||||
columnName: string;
|
||||
condition: 'eq' | 'neq' | 'like' | 'ilike' | 'gt' | 'gte' | 'lt' | 'lte';
|
||||
value?: any;
|
||||
}
|
||||
|
||||
export interface DataTableFilter {
|
||||
type?: 'and' | 'or';
|
||||
filters: DataTableFilterCondition[];
|
||||
}
|
||||
|
||||
export interface DataTableListParams {
|
||||
limit?: number;
|
||||
cursor?: string;
|
||||
}
|
||||
|
||||
export interface DataTableRowListParams {
|
||||
limit?: number;
|
||||
cursor?: string;
|
||||
filter?: string;
|
||||
sortBy?: string;
|
||||
search?: string;
|
||||
}
|
||||
|
||||
export interface DataTableInsertRowsParams {
|
||||
data: Record<string, unknown>[];
|
||||
returnType?: 'count' | 'id' | 'all';
|
||||
}
|
||||
|
||||
export interface DataTableUpdateRowsParams {
|
||||
filter: DataTableFilter;
|
||||
data: Record<string, unknown>;
|
||||
returnData?: boolean;
|
||||
dryRun?: boolean;
|
||||
}
|
||||
|
||||
export interface DataTableUpsertRowParams {
|
||||
filter: DataTableFilter;
|
||||
data: Record<string, unknown>;
|
||||
returnData?: boolean;
|
||||
dryRun?: boolean;
|
||||
}
|
||||
|
||||
export interface DataTableDeleteRowsParams {
|
||||
filter: string;
|
||||
returnData?: boolean;
|
||||
dryRun?: boolean;
|
||||
}
|
||||
@@ -124,6 +124,11 @@ export interface DeactivateWorkflowOperation extends DiffOperation {
|
||||
// No additional properties needed - just deactivates the workflow
|
||||
}
|
||||
|
||||
export interface TransferWorkflowOperation extends DiffOperation {
|
||||
type: 'transferWorkflow';
|
||||
destinationProjectId: string;
|
||||
}
|
||||
|
||||
// Connection Cleanup Operations
|
||||
export interface CleanStaleConnectionsOperation extends DiffOperation {
|
||||
type: 'cleanStaleConnections';
|
||||
@@ -161,7 +166,8 @@ export type WorkflowDiffOperation =
|
||||
| ActivateWorkflowOperation
|
||||
| DeactivateWorkflowOperation
|
||||
| CleanStaleConnectionsOperation
|
||||
| ReplaceConnectionsOperation;
|
||||
| ReplaceConnectionsOperation
|
||||
| TransferWorkflowOperation;
|
||||
|
||||
// Main diff request structure
|
||||
export interface WorkflowDiffRequest {
|
||||
@@ -192,6 +198,7 @@ export interface WorkflowDiffResult {
|
||||
shouldDeactivate?: boolean; // Flag to deactivate workflow after update (for deactivateWorkflow operation)
|
||||
tagsToAdd?: string[];
|
||||
tagsToRemove?: string[];
|
||||
transferToProjectId?: string; // For transferWorkflow operation - uses dedicated API call
|
||||
}
|
||||
|
||||
// Helper type for node reference (supports both ID and name)
|
||||
|
||||
@@ -22,8 +22,10 @@ export class N8nAuthenticationError extends N8nApiError {
|
||||
}
|
||||
|
||||
export class N8nNotFoundError extends N8nApiError {
|
||||
constructor(resource: string, id?: string) {
|
||||
const message = id ? `${resource} with ID ${id} not found` : `${resource} not found`;
|
||||
constructor(messageOrResource: string, id?: string) {
|
||||
// If id is provided, format as "resource with ID id not found"
|
||||
// Otherwise, use messageOrResource as-is (it's already a complete message from the API)
|
||||
const message = id ? `${messageOrResource} with ID ${id} not found` : messageOrResource;
|
||||
super(message, 404, 'NOT_FOUND');
|
||||
this.name = 'N8nNotFoundError';
|
||||
}
|
||||
@@ -70,7 +72,7 @@ export function handleN8nApiError(error: unknown): N8nApiError {
|
||||
case 401:
|
||||
return new N8nAuthenticationError(message);
|
||||
case 404:
|
||||
return new N8nNotFoundError('Resource', message);
|
||||
return new N8nNotFoundError(message || 'Resource');
|
||||
case 400:
|
||||
return new N8nValidationError(message, data);
|
||||
case 429:
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
# Performance Benchmarks
|
||||
|
||||
This directory contains performance benchmarks for critical operations in the n8n-mcp project.
|
||||
|
||||
## Running Benchmarks
|
||||
|
||||
### Local Development
|
||||
|
||||
```bash
|
||||
# Run all benchmarks
|
||||
npm run benchmark
|
||||
|
||||
# Watch mode for development
|
||||
npm run benchmark:watch
|
||||
|
||||
# Interactive UI
|
||||
npm run benchmark:ui
|
||||
|
||||
# Run specific benchmark file
|
||||
npx vitest bench tests/benchmarks/node-loading.bench.ts
|
||||
```
|
||||
|
||||
### CI/CD
|
||||
|
||||
Benchmarks run automatically on:
|
||||
- Every push to `main` branch
|
||||
- Every pull request
|
||||
- Manual workflow dispatch
|
||||
|
||||
## Benchmark Suites
|
||||
|
||||
### 1. Node Loading Performance (`node-loading.bench.ts`)
|
||||
- Package loading (n8n-nodes-base, @n8n/n8n-nodes-langchain)
|
||||
- Individual node file loading
|
||||
- Package.json parsing
|
||||
|
||||
### 2. Database Query Performance (`database-queries.bench.ts`)
|
||||
- Node retrieval by type
|
||||
- Category filtering
|
||||
- Search operations (OR, AND, FUZZY modes)
|
||||
- Node counting and statistics
|
||||
- Insert/update operations
|
||||
|
||||
### 3. Search Operations (`search-operations.bench.ts`)
|
||||
- Single and multi-word searches
|
||||
- Exact phrase matching
|
||||
- Fuzzy search performance
|
||||
- Property search within nodes
|
||||
- Complex filtering operations
|
||||
|
||||
### 4. Validation Performance (`validation-performance.bench.ts`)
|
||||
- Node configuration validation (minimal, strict, ai-friendly)
|
||||
- Expression validation
|
||||
- Workflow validation
|
||||
- Property dependency resolution
|
||||
|
||||
### 5. MCP Tool Execution (`mcp-tools.bench.ts`)
|
||||
- Tool execution overhead
|
||||
- Response formatting
|
||||
- Complex query handling
|
||||
|
||||
## Performance Targets
|
||||
|
||||
| Operation | Target | Alert Threshold |
|
||||
|-----------|--------|-----------------|
|
||||
| Node loading | <100ms per package | >150ms |
|
||||
| Database query | <5ms per query | >10ms |
|
||||
| Search (simple) | <10ms | >20ms |
|
||||
| Search (complex) | <50ms | >100ms |
|
||||
| Validation (simple) | <1ms | >2ms |
|
||||
| Validation (complex) | <10ms | >20ms |
|
||||
| MCP tool execution | <50ms | >100ms |
|
||||
|
||||
## Benchmark Results
|
||||
|
||||
- Results are tracked over time using GitHub Actions
|
||||
- Historical data available at: https://czlonkowski.github.io/n8n-mcp/benchmarks/
|
||||
- Performance regressions >10% trigger automatic alerts
|
||||
- PR comments show benchmark comparisons
|
||||
|
||||
## Writing New Benchmarks
|
||||
|
||||
```typescript
|
||||
import { bench, describe } from 'vitest';
|
||||
|
||||
describe('My Performance Suite', () => {
|
||||
bench('operation name', async () => {
|
||||
// Code to benchmark
|
||||
}, {
|
||||
iterations: 100, // Number of times to run
|
||||
warmupIterations: 10, // Warmup runs (not measured)
|
||||
warmupTime: 500, // Warmup duration in ms
|
||||
time: 3000 // Total benchmark duration in ms
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Isolate Operations**: Benchmark specific operations, not entire workflows
|
||||
2. **Use Realistic Data**: Load actual n8n nodes for realistic measurements
|
||||
3. **Warmup**: Always include warmup iterations to avoid JIT compilation effects
|
||||
4. **Memory**: Use in-memory databases for consistent results
|
||||
5. **Iterations**: Balance between accuracy and execution time
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Inconsistent Results
|
||||
- Increase `warmupIterations` and `warmupTime`
|
||||
- Run benchmarks in isolation
|
||||
- Check for background processes
|
||||
|
||||
### Memory Issues
|
||||
- Reduce `iterations` for memory-intensive operations
|
||||
- Add cleanup in `afterEach` hooks
|
||||
- Monitor memory usage during benchmarks
|
||||
|
||||
### CI Failures
|
||||
- Check benchmark timeout settings
|
||||
- Verify GitHub Actions runner resources
|
||||
- Review alert thresholds for false positives
|
||||
@@ -1,160 +0,0 @@
|
||||
import { bench, describe } from 'vitest';
|
||||
import { NodeRepository } from '../../src/database/node-repository';
|
||||
import { SQLiteStorageService } from '../../src/services/sqlite-storage-service';
|
||||
import { NodeFactory } from '../factories/node-factory';
|
||||
import { PropertyDefinitionFactory } from '../factories/property-definition-factory';
|
||||
|
||||
/**
|
||||
* Database Query Performance Benchmarks
|
||||
*
|
||||
* NOTE: These benchmarks use MOCK DATA (500 artificial test nodes)
|
||||
* created with factories, not the real production database.
|
||||
*
|
||||
* This is useful for tracking database layer performance in isolation,
|
||||
* but may not reflect real-world performance characteristics.
|
||||
*
|
||||
* For end-to-end MCP tool performance with real data, see mcp-tools.bench.ts
|
||||
*/
|
||||
describe('Database Query Performance', () => {
|
||||
let repository: NodeRepository;
|
||||
let storage: SQLiteStorageService;
|
||||
const testNodeCount = 500;
|
||||
|
||||
beforeAll(async () => {
|
||||
storage = new SQLiteStorageService(':memory:');
|
||||
repository = new NodeRepository(storage);
|
||||
|
||||
// Seed database with test data
|
||||
for (let i = 0; i < testNodeCount; i++) {
|
||||
const node = NodeFactory.build({
|
||||
displayName: `TestNode${i}`,
|
||||
nodeType: `nodes-base.testNode${i}`,
|
||||
category: i % 2 === 0 ? 'transform' : 'trigger',
|
||||
packageName: 'n8n-nodes-base',
|
||||
documentation: `Test documentation for node ${i}`,
|
||||
properties: PropertyDefinitionFactory.buildList(5)
|
||||
});
|
||||
await repository.upsertNode(node);
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
storage.close();
|
||||
});
|
||||
|
||||
bench('getNodeByType - existing node', async () => {
|
||||
await repository.getNodeByType('nodes-base.testNode100');
|
||||
}, {
|
||||
iterations: 1000,
|
||||
warmupIterations: 100,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('getNodeByType - non-existing node', async () => {
|
||||
await repository.getNodeByType('nodes-base.nonExistentNode');
|
||||
}, {
|
||||
iterations: 1000,
|
||||
warmupIterations: 100,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('getNodesByCategory - transform', async () => {
|
||||
await repository.getNodesByCategory('transform');
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('searchNodes - OR mode', async () => {
|
||||
await repository.searchNodes('test node data', 'OR', 20);
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('searchNodes - AND mode', async () => {
|
||||
await repository.searchNodes('test node', 'AND', 20);
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('searchNodes - FUZZY mode', async () => {
|
||||
await repository.searchNodes('tst nde', 'FUZZY', 20);
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('getAllNodes - no limit', async () => {
|
||||
await repository.getAllNodes();
|
||||
}, {
|
||||
iterations: 50,
|
||||
warmupIterations: 5,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('getAllNodes - with limit', async () => {
|
||||
await repository.getAllNodes(50);
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('getNodeCount', async () => {
|
||||
await repository.getNodeCount();
|
||||
}, {
|
||||
iterations: 1000,
|
||||
warmupIterations: 100,
|
||||
warmupTime: 100,
|
||||
time: 2000
|
||||
});
|
||||
|
||||
bench('getAIToolNodes', async () => {
|
||||
await repository.getAIToolNodes();
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('upsertNode - new node', async () => {
|
||||
const node = NodeFactory.build({
|
||||
displayName: `BenchNode${Date.now()}`,
|
||||
nodeType: `nodes-base.benchNode${Date.now()}`
|
||||
});
|
||||
await repository.upsertNode(node);
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('upsertNode - existing node update', async () => {
|
||||
const existingNode = await repository.getNodeByType('nodes-base.testNode0');
|
||||
if (existingNode) {
|
||||
existingNode.description = `Updated description ${Date.now()}`;
|
||||
await repository.upsertNode(existingNode);
|
||||
}
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
});
|
||||
@@ -1,3 +0,0 @@
|
||||
// Export all benchmark suites
|
||||
export * from './database-queries.bench';
|
||||
export * from './mcp-tools.bench';
|
||||
@@ -1,169 +0,0 @@
|
||||
import { bench, describe } from 'vitest';
|
||||
import { NodeRepository } from '../../src/database/node-repository';
|
||||
import { createDatabaseAdapter } from '../../src/database/database-adapter';
|
||||
import { EnhancedConfigValidator } from '../../src/services/enhanced-config-validator';
|
||||
import { PropertyFilter } from '../../src/services/property-filter';
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* MCP Tool Performance Benchmarks
|
||||
*
|
||||
* These benchmarks measure end-to-end performance of actual MCP tool operations
|
||||
* using the REAL production database (data/nodes.db with 525+ nodes).
|
||||
*
|
||||
* Unlike database-queries.bench.ts which uses mock data, these benchmarks
|
||||
* reflect what AI assistants actually experience when calling MCP tools,
|
||||
* making this the most meaningful performance metric for the system.
|
||||
*/
|
||||
describe('MCP Tool Performance (Production Database)', () => {
|
||||
let repository: NodeRepository;
|
||||
|
||||
beforeAll(async () => {
|
||||
// Use REAL production database
|
||||
const dbPath = path.join(__dirname, '../../data/nodes.db');
|
||||
const db = await createDatabaseAdapter(dbPath);
|
||||
repository = new NodeRepository(db);
|
||||
// Initialize similarity services for validation
|
||||
EnhancedConfigValidator.initializeSimilarityServices(repository);
|
||||
});
|
||||
|
||||
/**
|
||||
* search_nodes - Most frequently used tool for node discovery
|
||||
*
|
||||
* This measures:
|
||||
* - Database FTS5 full-text search
|
||||
* - Result filtering and ranking
|
||||
* - Response serialization
|
||||
*
|
||||
* Target: <20ms for common queries
|
||||
*/
|
||||
bench('search_nodes - common query (http)', async () => {
|
||||
await repository.searchNodes('http', 'OR', 20);
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('search_nodes - AI agent query (slack message)', async () => {
|
||||
await repository.searchNodes('slack send message', 'AND', 10);
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
/**
|
||||
* get_node_essentials - Fast retrieval of node configuration
|
||||
*
|
||||
* This measures:
|
||||
* - Database node lookup
|
||||
* - Property filtering (essentials only)
|
||||
* - Response formatting
|
||||
*
|
||||
* Target: <10ms for most nodes
|
||||
*/
|
||||
bench('get_node_essentials - HTTP Request node', async () => {
|
||||
const node = await repository.getNodeByType('n8n-nodes-base.httpRequest');
|
||||
if (node && node.properties) {
|
||||
PropertyFilter.getEssentials(node.properties, node.nodeType);
|
||||
}
|
||||
}, {
|
||||
iterations: 200,
|
||||
warmupIterations: 20,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('get_node_essentials - Slack node', async () => {
|
||||
const node = await repository.getNodeByType('n8n-nodes-base.slack');
|
||||
if (node && node.properties) {
|
||||
PropertyFilter.getEssentials(node.properties, node.nodeType);
|
||||
}
|
||||
}, {
|
||||
iterations: 200,
|
||||
warmupIterations: 20,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
/**
|
||||
* list_nodes - Initial exploration/listing
|
||||
*
|
||||
* This measures:
|
||||
* - Database query with pagination
|
||||
* - Result serialization
|
||||
* - Category filtering
|
||||
*
|
||||
* Target: <15ms for first page
|
||||
*/
|
||||
bench('list_nodes - first 50 nodes', async () => {
|
||||
await repository.getAllNodes(50);
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('list_nodes - AI tools only', async () => {
|
||||
await repository.getAIToolNodes();
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
/**
|
||||
* validate_node_operation - Configuration validation
|
||||
*
|
||||
* This measures:
|
||||
* - Schema lookup
|
||||
* - Validation logic execution
|
||||
* - Error message formatting
|
||||
*
|
||||
* Target: <15ms for simple validations
|
||||
*/
|
||||
bench('validate_node_operation - HTTP Request (minimal)', async () => {
|
||||
const node = await repository.getNodeByType('n8n-nodes-base.httpRequest');
|
||||
if (node && node.properties) {
|
||||
EnhancedConfigValidator.validateWithMode(
|
||||
'n8n-nodes-base.httpRequest',
|
||||
{},
|
||||
node.properties,
|
||||
'operation',
|
||||
'ai-friendly'
|
||||
);
|
||||
}
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('validate_node_operation - HTTP Request (with params)', async () => {
|
||||
const node = await repository.getNodeByType('n8n-nodes-base.httpRequest');
|
||||
if (node && node.properties) {
|
||||
EnhancedConfigValidator.validateWithMode(
|
||||
'n8n-nodes-base.httpRequest',
|
||||
{
|
||||
requestMethod: 'GET',
|
||||
url: 'https://api.example.com',
|
||||
authentication: 'none'
|
||||
},
|
||||
node.properties,
|
||||
'operation',
|
||||
'ai-friendly'
|
||||
);
|
||||
}
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
});
|
||||
@@ -1,204 +0,0 @@
|
||||
import { bench, describe } from 'vitest';
|
||||
import { MCPEngine } from '../../src/mcp-tools-engine';
|
||||
import { NodeRepository } from '../../src/database/node-repository';
|
||||
import { SQLiteStorageService } from '../../src/services/sqlite-storage-service';
|
||||
import { N8nNodeLoader } from '../../src/loaders/node-loader';
|
||||
|
||||
describe('MCP Tool Execution Performance', () => {
|
||||
let engine: MCPEngine;
|
||||
let storage: SQLiteStorageService;
|
||||
|
||||
beforeAll(async () => {
|
||||
storage = new SQLiteStorageService(':memory:');
|
||||
const repository = new NodeRepository(storage);
|
||||
const loader = new N8nNodeLoader(repository);
|
||||
await loader.loadPackage('n8n-nodes-base');
|
||||
|
||||
engine = new MCPEngine(repository);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
storage.close();
|
||||
});
|
||||
|
||||
bench('list_nodes - default limit', async () => {
|
||||
await engine.listNodes({});
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('list_nodes - large limit', async () => {
|
||||
await engine.listNodes({ limit: 200 });
|
||||
}, {
|
||||
iterations: 50,
|
||||
warmupIterations: 5,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('list_nodes - filtered by category', async () => {
|
||||
await engine.listNodes({ category: 'transform', limit: 100 });
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('search_nodes - single word', async () => {
|
||||
await engine.searchNodes({ query: 'http' });
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('search_nodes - multiple words', async () => {
|
||||
await engine.searchNodes({ query: 'http request webhook', mode: 'OR' });
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('get_node_info', async () => {
|
||||
await engine.getNodeInfo({ nodeType: 'n8n-nodes-base.httpRequest' });
|
||||
}, {
|
||||
iterations: 500,
|
||||
warmupIterations: 50,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('get_node_essentials', async () => {
|
||||
await engine.getNodeEssentials({ nodeType: 'n8n-nodes-base.httpRequest' });
|
||||
}, {
|
||||
iterations: 1000,
|
||||
warmupIterations: 100,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('get_node_documentation', async () => {
|
||||
await engine.getNodeDocumentation({ nodeType: 'n8n-nodes-base.httpRequest' });
|
||||
}, {
|
||||
iterations: 500,
|
||||
warmupIterations: 50,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('validate_node_operation - simple', async () => {
|
||||
await engine.validateNodeOperation({
|
||||
nodeType: 'n8n-nodes-base.httpRequest',
|
||||
config: {
|
||||
url: 'https://api.example.com',
|
||||
method: 'GET'
|
||||
},
|
||||
profile: 'minimal'
|
||||
});
|
||||
}, {
|
||||
iterations: 1000,
|
||||
warmupIterations: 100,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('validate_node_operation - complex', async () => {
|
||||
await engine.validateNodeOperation({
|
||||
nodeType: 'n8n-nodes-base.slack',
|
||||
config: {
|
||||
resource: 'message',
|
||||
operation: 'send',
|
||||
channel: 'C1234567890',
|
||||
text: 'Hello from benchmark'
|
||||
},
|
||||
profile: 'strict'
|
||||
});
|
||||
}, {
|
||||
iterations: 500,
|
||||
warmupIterations: 50,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('validate_node_minimal', async () => {
|
||||
await engine.validateNodeMinimal({
|
||||
nodeType: 'n8n-nodes-base.httpRequest',
|
||||
config: {}
|
||||
});
|
||||
}, {
|
||||
iterations: 2000,
|
||||
warmupIterations: 200,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('search_node_properties', async () => {
|
||||
await engine.searchNodeProperties({
|
||||
nodeType: 'n8n-nodes-base.httpRequest',
|
||||
query: 'authentication'
|
||||
});
|
||||
}, {
|
||||
iterations: 500,
|
||||
warmupIterations: 50,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('get_node_for_task', async () => {
|
||||
await engine.getNodeForTask({ task: 'post_json_request' });
|
||||
}, {
|
||||
iterations: 1000,
|
||||
warmupIterations: 100,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('list_ai_tools', async () => {
|
||||
await engine.listAITools({});
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('get_database_statistics', async () => {
|
||||
await engine.getDatabaseStatistics({});
|
||||
}, {
|
||||
iterations: 1000,
|
||||
warmupIterations: 100,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('validate_workflow - simple', async () => {
|
||||
await engine.validateWorkflow({
|
||||
workflow: {
|
||||
name: 'Test',
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Manual',
|
||||
type: 'n8n-nodes-base.manualTrigger',
|
||||
typeVersion: 1,
|
||||
position: [250, 300],
|
||||
parameters: {}
|
||||
}
|
||||
],
|
||||
connections: {}
|
||||
}
|
||||
});
|
||||
}, {
|
||||
iterations: 500,
|
||||
warmupIterations: 50,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
});
|
||||
@@ -1,2 +0,0 @@
|
||||
// This benchmark is temporarily disabled due to API changes in N8nNodeLoader
|
||||
// The benchmark needs to be updated to work with the new loader API
|
||||
@@ -1,59 +0,0 @@
|
||||
import { bench, describe } from 'vitest';
|
||||
import { N8nNodeLoader } from '../../src/loaders/node-loader';
|
||||
import { NodeRepository } from '../../src/database/node-repository';
|
||||
import { SQLiteStorageService } from '../../src/services/sqlite-storage-service';
|
||||
import path from 'path';
|
||||
|
||||
describe('Node Loading Performance', () => {
|
||||
let loader: N8nNodeLoader;
|
||||
let repository: NodeRepository;
|
||||
let storage: SQLiteStorageService;
|
||||
|
||||
beforeAll(() => {
|
||||
storage = new SQLiteStorageService(':memory:');
|
||||
repository = new NodeRepository(storage);
|
||||
loader = new N8nNodeLoader(repository);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
storage.close();
|
||||
});
|
||||
|
||||
bench('loadPackage - n8n-nodes-base', async () => {
|
||||
await loader.loadPackage('n8n-nodes-base');
|
||||
}, {
|
||||
iterations: 5,
|
||||
warmupIterations: 2,
|
||||
warmupTime: 1000,
|
||||
time: 5000
|
||||
});
|
||||
|
||||
bench('loadPackage - @n8n/n8n-nodes-langchain', async () => {
|
||||
await loader.loadPackage('@n8n/n8n-nodes-langchain');
|
||||
}, {
|
||||
iterations: 5,
|
||||
warmupIterations: 2,
|
||||
warmupTime: 1000,
|
||||
time: 5000
|
||||
});
|
||||
|
||||
bench('loadNodesFromPath - single file', async () => {
|
||||
const testPath = path.join(process.cwd(), 'node_modules/n8n-nodes-base/dist/nodes/HttpRequest');
|
||||
await loader.loadNodesFromPath(testPath, 'n8n-nodes-base');
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('parsePackageJson', async () => {
|
||||
const packageJsonPath = path.join(process.cwd(), 'node_modules/n8n-nodes-base/package.json');
|
||||
await loader['parsePackageJson'](packageJsonPath);
|
||||
}, {
|
||||
iterations: 1000,
|
||||
warmupIterations: 100,
|
||||
warmupTime: 100,
|
||||
time: 2000
|
||||
});
|
||||
});
|
||||
@@ -1,143 +0,0 @@
|
||||
import { bench, describe } from 'vitest';
|
||||
import { NodeRepository } from '../../src/database/node-repository';
|
||||
import { SQLiteStorageService } from '../../src/services/sqlite-storage-service';
|
||||
import { N8nNodeLoader } from '../../src/loaders/node-loader';
|
||||
|
||||
describe('Search Operations Performance', () => {
|
||||
let repository: NodeRepository;
|
||||
let storage: SQLiteStorageService;
|
||||
|
||||
beforeAll(async () => {
|
||||
storage = new SQLiteStorageService(':memory:');
|
||||
repository = new NodeRepository(storage);
|
||||
const loader = new N8nNodeLoader(repository);
|
||||
|
||||
// Load real nodes for realistic benchmarking
|
||||
await loader.loadPackage('n8n-nodes-base');
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
storage.close();
|
||||
});
|
||||
|
||||
bench('searchNodes - single word', async () => {
|
||||
await repository.searchNodes('http', 'OR', 20);
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('searchNodes - multiple words OR', async () => {
|
||||
await repository.searchNodes('http request webhook', 'OR', 20);
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('searchNodes - multiple words AND', async () => {
|
||||
await repository.searchNodes('http request', 'AND', 20);
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('searchNodes - fuzzy search', async () => {
|
||||
await repository.searchNodes('htpp requst', 'FUZZY', 20);
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('searchNodes - exact phrase', async () => {
|
||||
await repository.searchNodes('"HTTP Request"', 'OR', 20);
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('searchNodes - large result set', async () => {
|
||||
await repository.searchNodes('data', 'OR', 100);
|
||||
}, {
|
||||
iterations: 50,
|
||||
warmupIterations: 5,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('searchNodes - no results', async () => {
|
||||
await repository.searchNodes('xyznonexistentquery123', 'OR', 20);
|
||||
}, {
|
||||
iterations: 200,
|
||||
warmupIterations: 20,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('searchNodeProperties - common property', async () => {
|
||||
const node = await repository.getNodeByType('n8n-nodes-base.httpRequest');
|
||||
if (node) {
|
||||
await repository.searchNodeProperties(node.type, 'url', 20);
|
||||
}
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('searchNodeProperties - nested property', async () => {
|
||||
const node = await repository.getNodeByType('n8n-nodes-base.httpRequest');
|
||||
if (node) {
|
||||
await repository.searchNodeProperties(node.type, 'authentication', 20);
|
||||
}
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('getNodesByCategory - all categories', async () => {
|
||||
const categories = ['trigger', 'transform', 'output', 'input'];
|
||||
for (const category of categories) {
|
||||
await repository.getNodesByCategory(category);
|
||||
}
|
||||
}, {
|
||||
iterations: 50,
|
||||
warmupIterations: 5,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('getNodesByPackage', async () => {
|
||||
await repository.getNodesByPackage('n8n-nodes-base');
|
||||
}, {
|
||||
iterations: 50,
|
||||
warmupIterations: 5,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('complex filter - AI tools in transform category', async () => {
|
||||
const allNodes = await repository.getAllNodes();
|
||||
const filtered = allNodes.filter(node =>
|
||||
node.category === 'transform' &&
|
||||
node.isAITool
|
||||
);
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
});
|
||||
@@ -1,181 +0,0 @@
|
||||
import { bench, describe } from 'vitest';
|
||||
import { ConfigValidator } from '../../src/services/config-validator';
|
||||
import { EnhancedConfigValidator } from '../../src/services/enhanced-config-validator';
|
||||
import { ExpressionValidator } from '../../src/services/expression-validator';
|
||||
import { WorkflowValidator } from '../../src/services/workflow-validator';
|
||||
import { NodeRepository } from '../../src/database/node-repository';
|
||||
import { SQLiteStorageService } from '../../src/services/sqlite-storage-service';
|
||||
import { N8nNodeLoader } from '../../src/loaders/node-loader';
|
||||
|
||||
describe('Validation Performance', () => {
|
||||
let workflowValidator: WorkflowValidator;
|
||||
let repository: NodeRepository;
|
||||
let storage: SQLiteStorageService;
|
||||
|
||||
const simpleConfig = {
|
||||
url: 'https://api.example.com',
|
||||
method: 'GET',
|
||||
authentication: 'none'
|
||||
};
|
||||
|
||||
const complexConfig = {
|
||||
resource: 'message',
|
||||
operation: 'send',
|
||||
channel: 'C1234567890',
|
||||
text: 'Hello from benchmark',
|
||||
authentication: {
|
||||
type: 'oAuth2',
|
||||
credentials: {
|
||||
oauthTokenData: {
|
||||
access_token: 'xoxb-test-token'
|
||||
}
|
||||
}
|
||||
},
|
||||
options: {
|
||||
as_user: true,
|
||||
link_names: true,
|
||||
parse: 'full',
|
||||
reply_broadcast: false,
|
||||
thread_ts: '',
|
||||
unfurl_links: true,
|
||||
unfurl_media: true
|
||||
}
|
||||
};
|
||||
|
||||
const simpleWorkflow = {
|
||||
name: 'Simple Workflow',
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Manual Trigger',
|
||||
type: 'n8n-nodes-base.manualTrigger',
|
||||
typeVersion: 1,
|
||||
position: [250, 300] as [number, number],
|
||||
parameters: {}
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'HTTP Request',
|
||||
type: 'n8n-nodes-base.httpRequest',
|
||||
typeVersion: 4.2,
|
||||
position: [450, 300] as [number, number],
|
||||
parameters: {
|
||||
url: 'https://api.example.com',
|
||||
method: 'GET'
|
||||
}
|
||||
}
|
||||
],
|
||||
connections: {
|
||||
'1': {
|
||||
main: [
|
||||
[
|
||||
{
|
||||
node: '2',
|
||||
type: 'main',
|
||||
index: 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const complexWorkflow = {
|
||||
name: 'Complex Workflow',
|
||||
nodes: Array.from({ length: 20 }, (_, i) => ({
|
||||
id: `${i + 1}`,
|
||||
name: `Node ${i + 1}`,
|
||||
type: i % 3 === 0 ? 'n8n-nodes-base.httpRequest' :
|
||||
i % 3 === 1 ? 'n8n-nodes-base.slack' :
|
||||
'n8n-nodes-base.code',
|
||||
typeVersion: 1,
|
||||
position: [250 + (i % 5) * 200, 300 + Math.floor(i / 5) * 150] as [number, number],
|
||||
parameters: {
|
||||
url: '={{ $json.url }}',
|
||||
method: 'POST',
|
||||
body: '={{ JSON.stringify($json) }}',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
})),
|
||||
connections: Object.fromEntries(
|
||||
Array.from({ length: 19 }, (_, i) => [
|
||||
`${i + 1}`,
|
||||
{
|
||||
main: [[{ node: `${i + 2}`, type: 'main', index: 0 }]]
|
||||
}
|
||||
])
|
||||
)
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
storage = new SQLiteStorageService(':memory:');
|
||||
repository = new NodeRepository(storage);
|
||||
const loader = new N8nNodeLoader(repository);
|
||||
await loader.loadPackage('n8n-nodes-base');
|
||||
|
||||
workflowValidator = new WorkflowValidator(repository);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
storage.close();
|
||||
});
|
||||
|
||||
// Note: ConfigValidator and EnhancedConfigValidator have static methods,
|
||||
// so instance-based benchmarks are not applicable
|
||||
|
||||
bench('validateExpression - simple expression', async () => {
|
||||
ExpressionValidator.validateExpression('{{ $json.data }}');
|
||||
}, {
|
||||
iterations: 5000,
|
||||
warmupIterations: 500,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('validateExpression - complex expression', async () => {
|
||||
ExpressionValidator.validateExpression('{{ $node["HTTP Request"].json.items.map(item => item.id).join(",") }}');
|
||||
}, {
|
||||
iterations: 2000,
|
||||
warmupIterations: 200,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('validateWorkflow - simple workflow', async () => {
|
||||
await workflowValidator.validateWorkflow(simpleWorkflow);
|
||||
}, {
|
||||
iterations: 500,
|
||||
warmupIterations: 50,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('validateWorkflow - complex workflow', async () => {
|
||||
await workflowValidator.validateWorkflow(complexWorkflow);
|
||||
}, {
|
||||
iterations: 100,
|
||||
warmupIterations: 10,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('validateWorkflow - connections only', async () => {
|
||||
await workflowValidator.validateConnections(simpleWorkflow);
|
||||
}, {
|
||||
iterations: 1000,
|
||||
warmupIterations: 100,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
|
||||
bench('validateWorkflow - expressions only', async () => {
|
||||
await workflowValidator.validateExpressions(complexWorkflow);
|
||||
}, {
|
||||
iterations: 500,
|
||||
warmupIterations: 50,
|
||||
warmupTime: 500,
|
||||
time: 3000
|
||||
});
|
||||
});
|
||||
@@ -1,267 +0,0 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import {
|
||||
createTestDatabase,
|
||||
seedTestNodes,
|
||||
seedTestTemplates,
|
||||
createTestNode,
|
||||
createTestTemplate,
|
||||
createDatabaseSnapshot,
|
||||
restoreDatabaseSnapshot,
|
||||
loadFixtures,
|
||||
dbHelpers,
|
||||
TestDatabase
|
||||
} from '../utils/database-utils';
|
||||
import * as path from 'path';
|
||||
|
||||
/**
|
||||
* Example test file showing how to use database utilities
|
||||
* in real test scenarios
|
||||
*/
|
||||
|
||||
describe('Example: Using Database Utils in Tests', () => {
|
||||
let testDb: TestDatabase;
|
||||
|
||||
// Always cleanup after each test
|
||||
afterEach(async () => {
|
||||
if (testDb) {
|
||||
await testDb.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
describe('Basic Database Setup', () => {
|
||||
it('should setup a test database for unit testing', async () => {
|
||||
// Create an in-memory database for fast tests
|
||||
testDb = await createTestDatabase();
|
||||
|
||||
// Seed some test data
|
||||
await seedTestNodes(testDb.nodeRepository, [
|
||||
{ nodeType: 'nodes-base.myCustomNode', displayName: 'My Custom Node' }
|
||||
]);
|
||||
|
||||
// Use the repository to test your logic
|
||||
const node = testDb.nodeRepository.getNode('nodes-base.myCustomNode');
|
||||
expect(node).toBeDefined();
|
||||
expect(node.displayName).toBe('My Custom Node');
|
||||
});
|
||||
|
||||
it('should setup a file-based database for integration testing', async () => {
|
||||
// Create a file-based database when you need persistence
|
||||
testDb = await createTestDatabase({
|
||||
inMemory: false,
|
||||
dbPath: path.join(__dirname, '../temp/integration-test.db')
|
||||
});
|
||||
|
||||
// The database will persist until cleanup() is called
|
||||
await seedTestNodes(testDb.nodeRepository);
|
||||
|
||||
// You can verify the file exists
|
||||
expect(testDb.path).toContain('integration-test.db');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Testing with Fixtures', () => {
|
||||
it('should load complex test scenarios from fixtures', async () => {
|
||||
testDb = await createTestDatabase();
|
||||
|
||||
// Load fixtures from JSON file
|
||||
const fixturePath = path.join(__dirname, '../fixtures/database/test-nodes.json');
|
||||
await loadFixtures(testDb.adapter, fixturePath);
|
||||
|
||||
// Verify the fixture data was loaded
|
||||
expect(dbHelpers.countRows(testDb.adapter, 'nodes')).toBe(3);
|
||||
expect(dbHelpers.countRows(testDb.adapter, 'templates')).toBe(1);
|
||||
|
||||
// Test your business logic with the fixture data
|
||||
const slackNode = testDb.nodeRepository.getNode('nodes-base.slack');
|
||||
expect(slackNode.isAITool).toBe(true);
|
||||
expect(slackNode.category).toBe('Communication');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Testing Repository Methods', () => {
|
||||
beforeEach(async () => {
|
||||
testDb = await createTestDatabase();
|
||||
});
|
||||
|
||||
it('should test custom repository queries', async () => {
|
||||
// Seed nodes with specific properties
|
||||
await seedTestNodes(testDb.nodeRepository, [
|
||||
{ nodeType: 'nodes-base.ai1', isAITool: true },
|
||||
{ nodeType: 'nodes-base.ai2', isAITool: true },
|
||||
{ nodeType: 'nodes-base.regular', isAITool: false }
|
||||
]);
|
||||
|
||||
// Test custom queries
|
||||
const aiNodes = testDb.nodeRepository.getAITools();
|
||||
expect(aiNodes).toHaveLength(4); // 2 custom + 2 default (httpRequest, slack)
|
||||
|
||||
// Use dbHelpers for quick checks
|
||||
const allNodeTypes = dbHelpers.getAllNodeTypes(testDb.adapter);
|
||||
expect(allNodeTypes).toContain('nodes-base.ai1');
|
||||
expect(allNodeTypes).toContain('nodes-base.ai2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Testing with Snapshots', () => {
|
||||
it('should test rollback scenarios using snapshots', async () => {
|
||||
testDb = await createTestDatabase();
|
||||
|
||||
// Setup initial state
|
||||
await seedTestNodes(testDb.nodeRepository);
|
||||
await seedTestTemplates(testDb.templateRepository);
|
||||
|
||||
// Create a snapshot of the good state
|
||||
const snapshot = await createDatabaseSnapshot(testDb.adapter);
|
||||
|
||||
// Perform operations that might fail
|
||||
try {
|
||||
// Simulate a complex operation
|
||||
await testDb.nodeRepository.saveNode(createTestNode({
|
||||
nodeType: 'nodes-base.problematic',
|
||||
displayName: 'This might cause issues'
|
||||
}));
|
||||
|
||||
// Simulate an error
|
||||
throw new Error('Something went wrong!');
|
||||
} catch (error) {
|
||||
// Restore to the known good state
|
||||
await restoreDatabaseSnapshot(testDb.adapter, snapshot);
|
||||
}
|
||||
|
||||
// Verify we're back to the original state
|
||||
expect(dbHelpers.countRows(testDb.adapter, 'nodes')).toBe(snapshot.metadata.nodeCount);
|
||||
expect(dbHelpers.nodeExists(testDb.adapter, 'nodes-base.problematic')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Testing Database Performance', () => {
|
||||
it('should measure performance of database operations', async () => {
|
||||
testDb = await createTestDatabase();
|
||||
|
||||
// Measure bulk insert performance
|
||||
const insertDuration = await measureDatabaseOperation('Bulk Insert', async () => {
|
||||
const nodes = Array.from({ length: 100 }, (_, i) =>
|
||||
createTestNode({
|
||||
nodeType: `nodes-base.perf${i}`,
|
||||
displayName: `Performance Test Node ${i}`
|
||||
})
|
||||
);
|
||||
|
||||
for (const node of nodes) {
|
||||
testDb.nodeRepository.saveNode(node);
|
||||
}
|
||||
});
|
||||
|
||||
// Measure query performance
|
||||
const queryDuration = await measureDatabaseOperation('Query All Nodes', async () => {
|
||||
const allNodes = testDb.nodeRepository.getAllNodes();
|
||||
expect(allNodes.length).toBe(100); // 100 bulk nodes (no defaults as we're not using seedTestNodes)
|
||||
});
|
||||
|
||||
// Assert reasonable performance
|
||||
expect(insertDuration).toBeLessThan(1000); // Should complete in under 1 second
|
||||
expect(queryDuration).toBeLessThan(100); // Queries should be fast
|
||||
});
|
||||
});
|
||||
|
||||
describe('Testing with Different Database States', () => {
|
||||
it('should test behavior with empty database', async () => {
|
||||
testDb = await createTestDatabase();
|
||||
|
||||
// Test with empty database
|
||||
expect(dbHelpers.countRows(testDb.adapter, 'nodes')).toBe(0);
|
||||
|
||||
const nonExistentNode = testDb.nodeRepository.getNode('nodes-base.doesnotexist');
|
||||
expect(nonExistentNode).toBeNull();
|
||||
});
|
||||
|
||||
it('should test behavior with populated database', async () => {
|
||||
testDb = await createTestDatabase();
|
||||
|
||||
// Populate with many nodes
|
||||
const nodes = Array.from({ length: 50 }, (_, i) => ({
|
||||
nodeType: `nodes-base.node${i}`,
|
||||
displayName: `Node ${i}`,
|
||||
category: i % 2 === 0 ? 'Category A' : 'Category B'
|
||||
}));
|
||||
|
||||
await seedTestNodes(testDb.nodeRepository, nodes);
|
||||
|
||||
// Test queries on populated database
|
||||
const allNodes = dbHelpers.getAllNodeTypes(testDb.adapter);
|
||||
expect(allNodes.length).toBe(53); // 50 custom + 3 default
|
||||
|
||||
// Test filtering by category
|
||||
const categoryANodes = testDb.adapter
|
||||
.prepare('SELECT COUNT(*) as count FROM nodes WHERE category = ?')
|
||||
.get('Category A') as { count: number };
|
||||
|
||||
expect(categoryANodes.count).toBe(25);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Testing Error Scenarios', () => {
|
||||
it('should handle database errors gracefully', async () => {
|
||||
testDb = await createTestDatabase();
|
||||
|
||||
// Test saving invalid data
|
||||
const invalidNode = createTestNode({
|
||||
nodeType: '', // Invalid: empty nodeType
|
||||
displayName: 'Invalid Node'
|
||||
});
|
||||
|
||||
// SQLite allows NULL in PRIMARY KEY, so test with empty string instead
|
||||
// which should violate any business logic constraints
|
||||
// For now, we'll just verify the save doesn't crash
|
||||
expect(() => {
|
||||
testDb.nodeRepository.saveNode(invalidNode);
|
||||
}).not.toThrow();
|
||||
|
||||
// Database should still be functional
|
||||
await seedTestNodes(testDb.nodeRepository);
|
||||
expect(dbHelpers.countRows(testDb.adapter, 'nodes')).toBe(4); // 3 default nodes + 1 invalid node
|
||||
});
|
||||
});
|
||||
|
||||
describe('Testing with Transactions', () => {
|
||||
it('should test transactional behavior', async () => {
|
||||
testDb = await createTestDatabase();
|
||||
|
||||
// Seed initial data
|
||||
await seedTestNodes(testDb.nodeRepository);
|
||||
const initialCount = dbHelpers.countRows(testDb.adapter, 'nodes');
|
||||
|
||||
// Use transaction for atomic operations
|
||||
try {
|
||||
testDb.adapter.transaction(() => {
|
||||
// Add multiple nodes atomically
|
||||
testDb.nodeRepository.saveNode(createTestNode({ nodeType: 'nodes-base.tx1' }));
|
||||
testDb.nodeRepository.saveNode(createTestNode({ nodeType: 'nodes-base.tx2' }));
|
||||
|
||||
// Simulate error in transaction
|
||||
throw new Error('Transaction failed');
|
||||
});
|
||||
} catch (error) {
|
||||
// Transaction should have rolled back
|
||||
}
|
||||
|
||||
// Verify no nodes were added
|
||||
const finalCount = dbHelpers.countRows(testDb.adapter, 'nodes');
|
||||
expect(finalCount).toBe(initialCount);
|
||||
expect(dbHelpers.nodeExists(testDb.adapter, 'nodes-base.tx1')).toBe(false);
|
||||
expect(dbHelpers.nodeExists(testDb.adapter, 'nodes-base.tx2')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Helper function for performance measurement
|
||||
async function measureDatabaseOperation(
|
||||
name: string,
|
||||
operation: () => Promise<void>
|
||||
): Promise<number> {
|
||||
const start = performance.now();
|
||||
await operation();
|
||||
const duration = performance.now() - start;
|
||||
console.log(`[Performance] ${name}: ${duration.toFixed(2)}ms`);
|
||||
return duration;
|
||||
}
|
||||
@@ -43,7 +43,14 @@ describe.skipIf(!dbExists)('Database Content Validation', () => {
|
||||
// Ignore NODE_DB_PATH env var which might be set to :memory: by vitest
|
||||
db = await createDatabaseAdapter(dbPath);
|
||||
repository = new NodeRepository(db);
|
||||
console.log('✅ Database found - running validation tests');
|
||||
|
||||
// Rebuild FTS5 index to ensure it is in sync with the nodes table.
|
||||
// The content-synced FTS5 index (content=nodes) can become stale if the
|
||||
// database was rebuilt without an explicit FTS5 rebuild command, leaving
|
||||
// phantom rowid references that cause "missing row" errors on MATCH queries.
|
||||
db.prepare("INSERT INTO nodes_fts(nodes_fts) VALUES('rebuild')").run();
|
||||
|
||||
console.log('Database found - running validation tests');
|
||||
});
|
||||
|
||||
describe('[CRITICAL] Database Must Have Data', () => {
|
||||
|
||||
@@ -87,7 +87,7 @@ class InMemoryDatabaseAdapter implements DatabaseAdapter {
|
||||
|
||||
class InMemoryPreparedStatement implements PreparedStatement {
|
||||
run = vi.fn((...params: any[]): RunResult => {
|
||||
if (this.sql.includes('INSERT OR REPLACE INTO nodes')) {
|
||||
if (this.sql.includes('INSERT') && this.sql.includes('INTO nodes')) {
|
||||
const node = this.paramsToNode(params);
|
||||
this.adapter.saveNode(node);
|
||||
return { changes: 1, lastInsertRowid: 1 };
|
||||
@@ -100,6 +100,9 @@ class InMemoryPreparedStatement implements PreparedStatement {
|
||||
});
|
||||
|
||||
get = vi.fn((...params: any[]) => {
|
||||
if (this.sql.includes('SELECT npm_readme')) {
|
||||
return undefined; // No existing docs to preserve
|
||||
}
|
||||
if (this.sql.includes('SELECT * FROM nodes WHERE node_type = ?')) {
|
||||
return this.adapter.getNode(params[0]);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,12 @@ describe('Node FTS5 Search Integration Tests', () => {
|
||||
const testDbPath = './data/nodes.db';
|
||||
db = await createDatabaseAdapter(testDbPath);
|
||||
repository = new NodeRepository(db);
|
||||
|
||||
// Rebuild FTS5 index to ensure it is in sync with the nodes table.
|
||||
// The content-synced FTS5 index (content=nodes) can become stale if the
|
||||
// database was rebuilt without an explicit FTS5 rebuild command, leaving
|
||||
// phantom rowid references that cause "missing row" errors on MATCH queries.
|
||||
db.prepare("INSERT INTO nodes_fts(nodes_fts) VALUES('rebuild')").run();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user