mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-04-05 17:13: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]
|
||||
|
||||
## [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
4
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 [];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user