mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-03-23 19:03:07 +00:00
fix: resolve 5 bugs in n8n_manage_datatable (#651)
This commit is contained in:
committed by
GitHub
parent
be3d07dbdc
commit
c5665632af
@@ -2716,7 +2716,7 @@ const createTableSchema = z.object({
|
||||
name: z.string().min(1, 'Table name cannot be empty'),
|
||||
columns: z.array(z.object({
|
||||
name: z.string().min(1, 'Column name cannot be empty'),
|
||||
type: z.enum(['string', 'number', 'boolean', 'date', 'json']).optional(),
|
||||
type: z.enum(['string', 'number', 'boolean', 'date']).optional(),
|
||||
})).optional(),
|
||||
});
|
||||
|
||||
@@ -2729,29 +2729,40 @@ const updateTableSchema = tableIdSchema.extend({
|
||||
name: z.string().min(1, 'New table name cannot be empty'),
|
||||
});
|
||||
|
||||
// MCP transports may serialize JSON objects/arrays as strings.
|
||||
// Parse them back, but return the original value on failure so Zod reports a proper type error.
|
||||
function tryParseJson(val: unknown): unknown {
|
||||
if (typeof val !== 'string') return val;
|
||||
try { return JSON.parse(val); } catch { return val; }
|
||||
}
|
||||
|
||||
const coerceJsonArray = z.preprocess(tryParseJson, z.array(z.record(z.unknown())));
|
||||
const coerceJsonObject = z.preprocess(tryParseJson, z.record(z.unknown()));
|
||||
const coerceJsonFilter = z.preprocess(tryParseJson, dataTableFilterSchema);
|
||||
|
||||
const getRowsSchema = tableIdSchema.extend({
|
||||
limit: z.number().min(1).max(100).optional(),
|
||||
cursor: z.string().optional(),
|
||||
filter: z.union([dataTableFilterSchema, z.string()]).optional(),
|
||||
filter: z.union([coerceJsonFilter, z.string()]).optional(),
|
||||
sortBy: z.string().optional(),
|
||||
search: z.string().optional(),
|
||||
});
|
||||
|
||||
const insertRowsSchema = tableIdSchema.extend({
|
||||
data: z.array(z.record(z.unknown())).min(1, 'At least one row is required'),
|
||||
data: coerceJsonArray.pipe(z.array(z.record(z.unknown())).min(1, 'At least one row is required')),
|
||||
returnType: z.enum(['count', 'id', 'all']).optional(),
|
||||
});
|
||||
|
||||
// Shared schema for update/upsert (identical structure)
|
||||
const mutateRowsSchema = tableIdSchema.extend({
|
||||
filter: dataTableFilterSchema,
|
||||
data: z.record(z.unknown()),
|
||||
filter: coerceJsonFilter,
|
||||
data: coerceJsonObject,
|
||||
returnData: z.boolean().optional(),
|
||||
dryRun: z.boolean().optional(),
|
||||
});
|
||||
|
||||
const deleteRowsSchema = tableIdSchema.extend({
|
||||
filter: dataTableFilterSchema,
|
||||
filter: coerceJsonFilter,
|
||||
returnData: z.boolean().optional(),
|
||||
dryRun: z.boolean().optional(),
|
||||
});
|
||||
@@ -2847,10 +2858,14 @@ export async function handleDeleteTable(args: unknown, context?: InstanceContext
|
||||
export async function handleGetRows(args: unknown, context?: InstanceContext): Promise<McpToolResponse> {
|
||||
try {
|
||||
const client = ensureApiConfigured(context);
|
||||
const { tableId, filter, ...params } = getRowsSchema.parse(args);
|
||||
const { tableId, filter, sortBy, ...params } = getRowsSchema.parse(args);
|
||||
const queryParams: Record<string, unknown> = { ...params };
|
||||
if (filter) {
|
||||
queryParams.filter = typeof filter === 'string' ? filter : JSON.stringify(filter);
|
||||
const filterStr = typeof filter === 'string' ? filter : JSON.stringify(filter);
|
||||
queryParams.filter = encodeURIComponent(filterStr);
|
||||
}
|
||||
if (sortBy) {
|
||||
queryParams.sortBy = encodeURIComponent(sortBy);
|
||||
}
|
||||
const result = await client.getDataTableRows(tableId, queryParams as any);
|
||||
return {
|
||||
@@ -2916,7 +2931,7 @@ export async function handleDeleteRows(args: unknown, context?: InstanceContext)
|
||||
const client = ensureApiConfigured(context);
|
||||
const { tableId, filter, ...params } = deleteRowsSchema.parse(args);
|
||||
const queryParams = {
|
||||
filter: JSON.stringify(filter),
|
||||
filter: encodeURIComponent(JSON.stringify(filter)),
|
||||
...params,
|
||||
};
|
||||
const result = await client.deleteDataTableRows(tableId, queryParams as any);
|
||||
|
||||
@@ -42,7 +42,7 @@ export const n8nManageDatatableDoc: ToolDocumentation = {
|
||||
action: { type: 'string', required: true, description: 'Operation to perform' },
|
||||
tableId: { type: 'string', required: false, description: 'Data table ID (required for all except createTable and listTables)' },
|
||||
name: { type: 'string', required: false, description: 'For createTable/updateTable: table name' },
|
||||
columns: { type: 'array', required: false, description: 'For createTable: column definitions [{name, type?}]. Types: string, number, boolean, date, json' },
|
||||
columns: { type: 'array', required: false, description: 'For createTable: column definitions [{name, type?}]. Types: string, number, boolean, date' },
|
||||
data: { type: 'array|object', required: false, description: 'For insertRows: array of row objects. For updateRows/upsertRows: object with column values' },
|
||||
filter: { type: 'object', required: false, description: 'Filter: {type?: "and"|"or", filters: [{columnName, condition, value}]}' },
|
||||
limit: { type: 'number', required: false, description: 'For listTables/getRows: max results (1-100)' },
|
||||
|
||||
@@ -627,7 +627,7 @@ export const n8nManagementTools: ToolDefinition[] = [
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
type: { type: 'string', enum: ['string', 'number', 'boolean', 'date', 'json'] },
|
||||
type: { type: 'string', enum: ['string', 'number', 'boolean', 'date'] },
|
||||
},
|
||||
required: ['name'],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user