mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-04-06 17:43:08 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1750fb4acf |
10
CHANGELOG.md
10
CHANGELOG.md
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [2.47.1] - 2026-04-04
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Credential get fallback** — `n8n_manage_credentials({action: "get"})` now falls back to list + filter when `GET /credentials/:id` returns 403 Forbidden or 405 Method Not Allowed, since this endpoint is not in the n8n public API
|
||||||
|
- **Credential update accepts `type` field** — `n8n_manage_credentials({action: "update"})` now forwards the optional `type` field to the n8n API, which some n8n versions require in the PATCH payload
|
||||||
|
- **Credential response stripping** — `create` and `update` handlers now strip the `data` field from responses (defense-in-depth, matching the `get` handler pattern)
|
||||||
|
|
||||||
|
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
|
||||||
|
|
||||||
## [2.47.0] - 2026-04-04
|
## [2.47.0] - 2026-04-04
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "n8n-mcp",
|
"name": "n8n-mcp",
|
||||||
"version": "2.47.0",
|
"version": "2.47.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "n8n-mcp",
|
"name": "n8n-mcp",
|
||||||
"version": "2.47.0",
|
"version": "2.47.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "1.28.0",
|
"@modelcontextprotocol/sdk": "1.28.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "n8n-mcp",
|
"name": "n8n-mcp",
|
||||||
"version": "2.47.0",
|
"version": "2.47.1",
|
||||||
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
|
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|||||||
@@ -2994,6 +2994,7 @@ const createCredentialSchema = z.object({
|
|||||||
const updateCredentialSchema = z.object({
|
const updateCredentialSchema = z.object({
|
||||||
id: z.string({ required_error: 'Credential ID is required' }),
|
id: z.string({ required_error: 'Credential ID is required' }),
|
||||||
name: z.string().optional(),
|
name: z.string().optional(),
|
||||||
|
type: z.string().optional(),
|
||||||
data: z.record(z.any()).optional(),
|
data: z.record(z.any()).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -3027,7 +3028,23 @@ export async function handleGetCredential(args: unknown, context?: InstanceConte
|
|||||||
try {
|
try {
|
||||||
const client = ensureApiConfigured(context);
|
const client = ensureApiConfigured(context);
|
||||||
const { id } = getCredentialSchema.parse(args);
|
const { id } = getCredentialSchema.parse(args);
|
||||||
const credential = await client.getCredential(id);
|
let credential;
|
||||||
|
try {
|
||||||
|
credential = await client.getCredential(id);
|
||||||
|
} catch (getError: unknown) {
|
||||||
|
// GET /credentials/:id is not in the n8n public API — fall back to list + filter
|
||||||
|
const status = (getError as { statusCode?: number }).statusCode;
|
||||||
|
const msg = (getError as Error).message ?? '';
|
||||||
|
const isUnsupported = status === 405 || status === 403 || msg.includes('not allowed');
|
||||||
|
if (!isUnsupported) {
|
||||||
|
throw getError;
|
||||||
|
}
|
||||||
|
const list = await client.listCredentials();
|
||||||
|
credential = list.data.find((c) => c.id === id);
|
||||||
|
if (!credential) {
|
||||||
|
return { success: false, error: `Credential ${id} not found` };
|
||||||
|
}
|
||||||
|
}
|
||||||
// Strip sensitive data field — defense in depth against future n8n versions returning decrypted values
|
// Strip sensitive data field — defense in depth against future n8n versions returning decrypted values
|
||||||
const { data: _sensitiveData, ...safeCred } = credential;
|
const { data: _sensitiveData, ...safeCred } = credential;
|
||||||
return {
|
return {
|
||||||
@@ -3059,10 +3076,11 @@ export async function handleCreateCredential(args: unknown, context?: InstanceCo
|
|||||||
export async function handleUpdateCredential(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
|
export async function handleUpdateCredential(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
|
||||||
try {
|
try {
|
||||||
const client = ensureApiConfigured(context);
|
const client = ensureApiConfigured(context);
|
||||||
const { id, name, data } = updateCredentialSchema.parse(args);
|
const { id, name, type, data } = updateCredentialSchema.parse(args);
|
||||||
logger.info(`Updating credential: id="${id}"${name ? `, name="${name}"` : ''}`);
|
logger.info(`Updating credential: id="${id}"${name ? `, name="${name}"` : ''}`);
|
||||||
const updatePayload: Record<string, any> = {};
|
const updatePayload: Record<string, any> = {};
|
||||||
if (name !== undefined) updatePayload.name = name;
|
if (name !== undefined) updatePayload.name = name;
|
||||||
|
if (type !== undefined) updatePayload.type = type;
|
||||||
if (data !== undefined) updatePayload.data = data;
|
if (data !== undefined) updatePayload.data = data;
|
||||||
const credential = await client.updateCredential(id, updatePayload);
|
const credential = await client.updateCredential(id, updatePayload);
|
||||||
const { data: _sensitiveData, ...safeCred } = credential;
|
const { data: _sensitiveData, ...safeCred } = credential;
|
||||||
|
|||||||
@@ -135,13 +135,7 @@ export class N8nApiClient {
|
|||||||
* Internal method to fetch version once
|
* Internal method to fetch version once
|
||||||
*/
|
*/
|
||||||
private async fetchVersionOnce(): Promise<N8nVersionInfo | null> {
|
private async fetchVersionOnce(): Promise<N8nVersionInfo | null> {
|
||||||
// Check if already cached globally
|
return getCachedVersion(this.baseUrl) ?? await fetchN8nVersion(this.baseUrl);
|
||||||
let version = getCachedVersion(this.baseUrl);
|
|
||||||
if (!version) {
|
|
||||||
// Fetch from server
|
|
||||||
version = await fetchN8nVersion(this.baseUrl);
|
|
||||||
}
|
|
||||||
return version;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -248,10 +248,11 @@ function checkDataRetentionSettings(workflow: WorkflowInput): AuditFinding[] {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const savesAllErrors = settings.saveDataErrorExecution === 'all';
|
const savesAllData =
|
||||||
const savesAllSuccess = settings.saveDataSuccessExecution === 'all';
|
settings.saveDataErrorExecution === 'all' &&
|
||||||
|
settings.saveDataSuccessExecution === 'all';
|
||||||
|
|
||||||
if (!savesAllErrors || !savesAllSuccess) {
|
if (!savesAllData) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user