Skip to content

Commit 1750fb4

Browse files
czlonkowskiclaude
andauthored
fix: credential get fallback, update type field, and code refinements (v2.47.1) (czlonkowski#703)
- GET /credentials/:id is not in the n8n public API; fall back to list + filter, catching both 403 and 405 responses - Forward optional `type` field in credential update for n8n versions that require it in PATCH payload - Strip `data` field from create/update credential responses - Code simplification: typed error handling, consolidated booleans, null-coalescing in version fetch Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 796c427 commit 1750fb4

6 files changed

Lines changed: 38 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [2.47.1] - 2026-04-04
11+
12+
### Fixed
13+
14+
- **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
15+
- **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
16+
- **Credential response stripping**`create` and `update` handlers now strip the `data` field from responses (defense-in-depth, matching the `get` handler pattern)
17+
18+
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
19+
1020
## [2.47.0] - 2026-04-04
1121

1222
### Added

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "n8n-mcp",
3-
"version": "2.47.0",
3+
"version": "2.47.1",
44
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

src/mcp/handlers-n8n-manager.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2994,6 +2994,7 @@ const createCredentialSchema = z.object({
29942994
const updateCredentialSchema = z.object({
29952995
id: z.string({ required_error: 'Credential ID is required' }),
29962996
name: z.string().optional(),
2997+
type: z.string().optional(),
29972998
data: z.record(z.any()).optional(),
29982999
});
29993000

@@ -3027,7 +3028,23 @@ export async function handleGetCredential(args: unknown, context?: InstanceConte
30273028
try {
30283029
const client = ensureApiConfigured(context);
30293030
const { id } = getCredentialSchema.parse(args);
3030-
const credential = await client.getCredential(id);
3031+
let credential;
3032+
try {
3033+
credential = await client.getCredential(id);
3034+
} catch (getError: unknown) {
3035+
// GET /credentials/:id is not in the n8n public API — fall back to list + filter
3036+
const status = (getError as { statusCode?: number }).statusCode;
3037+
const msg = (getError as Error).message ?? '';
3038+
const isUnsupported = status === 405 || status === 403 || msg.includes('not allowed');
3039+
if (!isUnsupported) {
3040+
throw getError;
3041+
}
3042+
const list = await client.listCredentials();
3043+
credential = list.data.find((c) => c.id === id);
3044+
if (!credential) {
3045+
return { success: false, error: `Credential ${id} not found` };
3046+
}
3047+
}
30313048
// Strip sensitive data field — defense in depth against future n8n versions returning decrypted values
30323049
const { data: _sensitiveData, ...safeCred } = credential;
30333050
return {
@@ -3059,10 +3076,11 @@ export async function handleCreateCredential(args: unknown, context?: InstanceCo
30593076
export async function handleUpdateCredential(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
30603077
try {
30613078
const client = ensureApiConfigured(context);
3062-
const { id, name, data } = updateCredentialSchema.parse(args);
3079+
const { id, name, type, data } = updateCredentialSchema.parse(args);
30633080
logger.info(`Updating credential: id="${id}"${name ? `, name="${name}"` : ''}`);
30643081
const updatePayload: Record<string, any> = {};
30653082
if (name !== undefined) updatePayload.name = name;
3083+
if (type !== undefined) updatePayload.type = type;
30663084
if (data !== undefined) updatePayload.data = data;
30673085
const credential = await client.updateCredential(id, updatePayload);
30683086
const { data: _sensitiveData, ...safeCred } = credential;

src/services/n8n-api-client.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -135,13 +135,7 @@ export class N8nApiClient {
135135
* Internal method to fetch version once
136136
*/
137137
private async fetchVersionOnce(): Promise<N8nVersionInfo | null> {
138-
// Check if already cached globally
139-
let version = getCachedVersion(this.baseUrl);
140-
if (!version) {
141-
// Fetch from server
142-
version = await fetchN8nVersion(this.baseUrl);
143-
}
144-
return version;
138+
return getCachedVersion(this.baseUrl) ?? await fetchN8nVersion(this.baseUrl);
145139
}
146140

147141
/**

src/services/workflow-security-scanner.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,10 +248,11 @@ function checkDataRetentionSettings(workflow: WorkflowInput): AuditFinding[] {
248248
return [];
249249
}
250250

251-
const savesAllErrors = settings.saveDataErrorExecution === 'all';
252-
const savesAllSuccess = settings.saveDataSuccessExecution === 'all';
251+
const savesAllData =
252+
settings.saveDataErrorExecution === 'all' &&
253+
settings.saveDataSuccessExecution === 'all';
253254

254-
if (!savesAllErrors || !savesAllSuccess) {
255+
if (!savesAllData) {
255256
return [];
256257
}
257258

0 commit comments

Comments
 (0)