fix: defensive JSON.parse for stringified object/array params (#605) (#606)

* chore: update n8n to 2.8.3 and bump version to 2.35.3

- Updated n8n from 2.6.3 to 2.8.3
- Updated n8n-core from 2.6.1 to 2.8.1
- Updated n8n-workflow from 2.6.0 to 2.8.0
- Updated @n8n/n8n-nodes-langchain from 2.6.2 to 2.8.1
- Fixed node loader to bypass restricted package.json exports in
  @n8n/n8n-nodes-langchain >=2.9.0 (resolves via absolute paths)
- Fixed community doc generator for cloud LLMs: added API key env var
  support, switched to max_completion_tokens, auto-omit temperature
- Rebuilt node database with 1,236 nodes (673 n8n-nodes-base,
  133 @n8n/n8n-nodes-langchain, 430 community)
- Refreshed community nodes (361 verified + 69 npm) with 424 AI summaries
- Updated README badge with new n8n version and node counts
- Updated CHANGELOG with dependency changes

Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: update documentation-generator tests for max_completion_tokens

- Updated test assertions from max_tokens to max_completion_tokens
- Updated testConnection token limit expectation from 10 to 200
- Added temperature to test config to match new configurable behavior

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: defensive JSON.parse for stringified object/array params (#605)

Claude Desktop 1.1.3189 serializes object/array MCP parameters as JSON
strings, causing ZodError failures for ~60% of tools. Add schema-driven
coercion in the central CallToolRequestSchema handler to detect and parse
them back automatically.

Conceived by Romuald Czlonkowski - https://www.aiadvisors.pl/en

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Romuald Członkowski
2026-02-20 16:56:25 +01:00
committed by GitHub
parent 77048347b3
commit 4bad880f44
4 changed files with 272 additions and 2 deletions

View File

@@ -720,7 +720,12 @@ export class N8NDocumentationMCPServer {
});
}
}
// Workaround for Claude Desktop 1.1.3189 string serialization bug.
// The MCP client serializes object/array parameters as JSON strings.
// Use the tool's inputSchema to detect and parse them back.
processedArgs = this.coerceStringifiedJsonParams(name, processedArgs);
try {
logger.debug(`Executing tool: ${name}`, { args: processedArgs });
const startTime = Date.now();
@@ -1125,6 +1130,50 @@ export class N8NDocumentationMCPServer {
return true;
}
/**
* Coerce stringified JSON parameters back to objects/arrays.
* Workaround for Claude Desktop 1.1.3189 which serializes object/array
* params as JSON strings before sending them to MCP servers.
*/
private coerceStringifiedJsonParams(
toolName: string,
args: Record<string, any> | undefined
): Record<string, any> | undefined {
if (!args || typeof args !== 'object') return args;
const allTools = [...n8nDocumentationToolsFinal, ...n8nManagementTools];
const tool = allTools.find(t => t.name === toolName);
if (!tool?.inputSchema?.properties) return args;
const properties = tool.inputSchema.properties;
const coerced = { ...args };
for (const [key, value] of Object.entries(coerced)) {
if (typeof value !== 'string') continue;
const expectedType = (properties as any)[key]?.type;
if (expectedType !== 'object' && expectedType !== 'array') continue;
const trimmed = value.trim();
const validPrefix = (expectedType === 'object' && trimmed.startsWith('{'))
|| (expectedType === 'array' && trimmed.startsWith('['));
if (!validPrefix) continue;
try {
const parsed = JSON.parse(trimmed);
const isArray = Array.isArray(parsed);
if ((expectedType === 'object' && typeof parsed === 'object' && !isArray)
|| (expectedType === 'array' && isArray)) {
coerced[key] = parsed;
logger.warn(`Coerced stringified ${expectedType} param "${key}" for tool "${toolName}"`);
}
} catch {
// Not valid JSON — keep original string, downstream validation will report the error
}
}
return coerced;
}
async executeTool(name: string, args: any): Promise<any> {
// Ensure args is an object and validate it
args = args || {};