fix: credential get fallback, update type field, and code refinements (v2.47.1) (#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>
This commit is contained in:
Romuald Członkowski
2026-04-04 13:09:03 +02:00
committed by GitHub
parent 796c427317
commit 1750fb4acf
6 changed files with 38 additions and 15 deletions

View File

@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [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
### Added

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "n8n-mcp",
"version": "2.47.0",
"version": "2.47.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "n8n-mcp",
"version": "2.47.0",
"version": "2.47.1",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "1.28.0",

View File

@@ -1,6 +1,6 @@
{
"name": "n8n-mcp",
"version": "2.47.0",
"version": "2.47.1",
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
"main": "dist/index.js",
"types": "dist/index.d.ts",

View File

@@ -2994,6 +2994,7 @@ const createCredentialSchema = z.object({
const updateCredentialSchema = z.object({
id: z.string({ required_error: 'Credential ID is required' }),
name: z.string().optional(),
type: z.string().optional(),
data: z.record(z.any()).optional(),
});
@@ -3027,7 +3028,23 @@ export async function handleGetCredential(args: unknown, context?: InstanceConte
try {
const client = ensureApiConfigured(context);
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
const { data: _sensitiveData, ...safeCred } = credential;
return {
@@ -3059,10 +3076,11 @@ export async function handleCreateCredential(args: unknown, context?: InstanceCo
export async function handleUpdateCredential(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
try {
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}"` : ''}`);
const updatePayload: Record<string, any> = {};
if (name !== undefined) updatePayload.name = name;
if (type !== undefined) updatePayload.type = type;
if (data !== undefined) updatePayload.data = data;
const credential = await client.updateCredential(id, updatePayload);
const { data: _sensitiveData, ...safeCred } = credential;

View File

@@ -135,13 +135,7 @@ export class N8nApiClient {
* Internal method to fetch version once
*/
private async fetchVersionOnce(): Promise<N8nVersionInfo | null> {
// Check if already cached globally
let version = getCachedVersion(this.baseUrl);
if (!version) {
// Fetch from server
version = await fetchN8nVersion(this.baseUrl);
}
return version;
return getCachedVersion(this.baseUrl) ?? await fetchN8nVersion(this.baseUrl);
}
/**

View File

@@ -248,10 +248,11 @@ function checkDataRetentionSettings(workflow: WorkflowInput): AuditFinding[] {
return [];
}
const savesAllErrors = settings.saveDataErrorExecution === 'all';
const savesAllSuccess = settings.saveDataSuccessExecution === 'all';
const savesAllData =
settings.saveDataErrorExecution === 'all' &&
settings.saveDataSuccessExecution === 'all';
if (!savesAllErrors || !savesAllSuccess) {
if (!savesAllData) {
return [];
}