refactor(core): Trim narration-only descriptions from instance MCP tools

Tier 1 of the MCP tool-description slimming experiment: remove tool
descriptions that only restate the tool name or its parameters, and
param describe() texts that only restate the parameter name or a
constraint already encoded in the schema (e.g. max limits). Descriptions
carrying real behavioral content are kept or reduced to that content.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016zz22Mze4HQVLEoD6HYjnz
This commit is contained in:
Claude 2026-07-29 13:24:02 +00:00
parent 23622769e6
commit 2ff574b94a
No known key found for this signature in database
32 changed files with 48 additions and 103 deletions

View File

@ -37,7 +37,7 @@ describe('add_data_table_rows MCP tool', () => {
expect(tool.name).toBe('add_data_table_rows');
expect(tool.config).toBeDefined();
expect(typeof tool.config.description).toBe('string');
expect(tool.config.description).toBeUndefined();
expect(tool.config.inputSchema).toBeDefined();
expect(tool.config.outputSchema).toBeDefined();
expect(typeof tool.handler).toBe('function');

View File

@ -43,7 +43,7 @@ describe('create_data_table MCP tool', () => {
expect(tool.name).toBe('create_data_table');
expect(tool.config).toBeDefined();
expect(typeof tool.config.description).toBe('string');
expect(tool.config.description).toBeUndefined();
expect(tool.config.inputSchema).toBeDefined();
expect(tool.config.outputSchema).toBeDefined();
expect(typeof tool.handler).toBe('function');

View File

@ -49,7 +49,7 @@ describe('search_data_tables MCP tool', () => {
expect(tool.name).toBe('search_data_tables');
expect(tool.config).toBeDefined();
expect(typeof tool.config.description).toBe('string');
expect(tool.config.description).toBeUndefined();
expect(tool.config.inputSchema).toBeDefined();
expect(tool.config.outputSchema).toBeDefined();
expect(typeof tool.handler).toBe('function');

View File

@ -44,8 +44,7 @@ describe('get-execution MCP tool', () => {
expect(tool.name).toBe('get_execution');
expect(tool.config).toBeDefined();
expect(typeof tool.config.description).toBe('string');
expect(tool.config.description).toContain('Get execution details');
expect(tool.config.description).toBeUndefined();
expect(tool.config.inputSchema).toBeDefined();
expect(tool.config.outputSchema).toBeDefined();
expect(typeof tool.handler).toBe('function');

View File

@ -49,7 +49,7 @@ describe('list-tags MCP tool', () => {
const tool = createListTagsTool(user, tagService, telemetry);
expect(tool.name).toBe('list_tags');
expect(tool.config.description).toEqual(expect.any(String));
expect(tool.config.description).toBeUndefined();
expect(tool.config.inputSchema).toBeDefined();
expect(tool.config.outputSchema).toBeDefined();
expect(tool.config.annotations).toMatchObject({

View File

@ -60,7 +60,7 @@ describe('search-folders MCP tool', () => {
expect(tool.name).toBe('search_folders');
expect(tool.config).toBeDefined();
expect(typeof tool.config.description).toBe('string');
expect(tool.config.description).toBeUndefined();
expect(tool.config.inputSchema).toBeDefined();
expect(typeof tool.handler).toBe('function');
});

View File

@ -47,7 +47,7 @@ describe('search-workflows MCP tool', () => {
expect(tool.name).toBe('search_workflows');
expect(tool.config).toBeDefined();
expect(typeof tool.config.description).toBe('string');
expect(tool.config.description).toBeUndefined();
expect(tool.config.inputSchema).toBeDefined();
expect(typeof tool.handler).toBe('function');
});

View File

@ -41,8 +41,7 @@ describe('unpublish-workflow MCP tool', () => {
expect(tool.name).toBe('unpublish_workflow');
expect(tool.config).toBeDefined();
expect(typeof tool.config.description).toBe('string');
expect(tool.config.description).toContain('Unpublish');
expect(tool.config.description).toBeUndefined();
expect(tool.config.inputSchema).toBeDefined();
expect(tool.config.outputSchema).toBeDefined();
expect(typeof tool.handler).toBe('function');

View File

@ -14,10 +14,10 @@ import {
} from '../schemas';
const inputSchema = {
dataTableId: z.string().describe('The ID of the data table to add a column to'),
dataTableId: z.string(),
projectId: dataTableProjectIdSchema,
name: columnNameSchema,
type: dataTableColumnTypeSchema.describe('The data type of the new column'),
type: dataTableColumnTypeSchema,
} satisfies z.ZodRawShape;
const outputSchema = {
@ -33,7 +33,6 @@ export const createAddDataTableColumnTool = (
): ToolDefinition<typeof inputSchema> => ({
name: 'add_data_table_column',
config: {
description: 'Add a new column to an existing data table.',
inputSchema,
outputSchema,
annotations: {

View File

@ -11,15 +11,13 @@ import { dataTableProjectIdSchema } from '../schemas';
const ADD_ROWS_MAX = 1000;
const addRowsInputSchema = {
dataTableId: z.string().describe('The ID of the data table to insert rows into'),
dataTableId: z.string(),
projectId: dataTableProjectIdSchema,
rows: z
.array(z.record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.null()])))
.min(1)
.max(ADD_ROWS_MAX)
.describe(
`Array of row objects to insert. Each object maps column names to values. Maximum ${ADD_ROWS_MAX} rows per call.`,
),
.describe('Row objects mapping column names to values.'),
} satisfies z.ZodRawShape;
const addRowsOutputSchema = {
@ -34,8 +32,6 @@ export const createAddDataTableRowsTool = (
): ToolDefinition<typeof addRowsInputSchema> => ({
name: 'add_data_table_rows',
config: {
description:
'Insert rows into an existing data table. Each row is an object mapping column names to values. Use search_data_tables to find the data table ID first.',
inputSchema: addRowsInputSchema,
outputSchema: addRowsOutputSchema,
annotations: {

View File

@ -14,16 +14,13 @@ const columnSchema = z.object({
});
const createInputSchema = {
projectId: z.string().describe('The project ID where the data table will be created'),
projectId: z.string(),
name: z
.string()
.min(1)
.max(128)
.describe('The name of the data table (must be unique within the project)'),
columns: z
.array(columnSchema)
.min(1)
.describe('The columns to create in the data table. At least one column is required.'),
columns: z.array(columnSchema).min(1),
} satisfies z.ZodRawShape;
const createOutputSchema = {
@ -39,8 +36,6 @@ export const createCreateDataTableTool = (
): ToolDefinition<typeof createInputSchema> => ({
name: 'create_data_table',
config: {
description:
'Create a new data table with the specified columns. Use search_projects to find a project ID first.',
inputSchema: createInputSchema,
outputSchema: createOutputSchema,
annotations: {

View File

@ -9,9 +9,9 @@ import type { ToolDefinition, UserCalledMCPToolEventPayload } from '../../mcp.ty
import { dataTableProjectIdSchema, successMessageOutputSchema } from '../schemas';
const inputSchema = {
dataTableId: z.string().describe('The ID of the data table containing the column'),
dataTableId: z.string(),
projectId: dataTableProjectIdSchema,
columnId: z.string().describe('The ID of the column to delete'),
columnId: z.string(),
} satisfies z.ZodRawShape;
const outputSchema = successMessageOutputSchema;
@ -23,8 +23,7 @@ export const createDeleteDataTableColumnTool = (
): ToolDefinition<typeof inputSchema> => ({
name: 'delete_data_table_column',
config: {
description:
'Delete a column from a data table. This permanently removes the column and all its data.',
description: 'Permanently removes the column and all its data.',
inputSchema,
outputSchema,
annotations: {

View File

@ -14,10 +14,10 @@ import {
} from '../schemas';
const inputSchema = {
dataTableId: z.string().describe('The ID of the data table containing the column'),
dataTableId: z.string(),
projectId: dataTableProjectIdSchema,
columnId: z.string().describe('The ID of the column to rename'),
name: columnNameSchema.describe('The new column name'),
columnId: z.string(),
name: columnNameSchema,
} satisfies z.ZodRawShape;
const outputSchema = {
@ -32,7 +32,6 @@ export const createRenameDataTableColumnTool = (
): ToolDefinition<typeof inputSchema> => ({
name: 'rename_data_table_column',
config: {
description: 'Rename a column in a data table.',
inputSchema,
outputSchema,
annotations: {

View File

@ -9,9 +9,9 @@ import type { ToolDefinition, UserCalledMCPToolEventPayload } from '../../mcp.ty
import { dataTableProjectIdSchema, successMessageOutputSchema } from '../schemas';
const inputSchema = {
dataTableId: z.string().describe('The ID of the data table to rename'),
dataTableId: z.string(),
projectId: dataTableProjectIdSchema,
name: z.string().min(1).max(128).describe('The new name for the data table'),
name: z.string().min(1).max(128),
} satisfies z.ZodRawShape;
const outputSchema = successMessageOutputSchema;
@ -23,7 +23,6 @@ export const createRenameDataTableTool = (
): ToolDefinition<typeof inputSchema> => ({
name: 'rename_data_table',
config: {
description: 'Rename an existing data table.',
inputSchema,
outputSchema,
annotations: {

View File

@ -16,7 +16,7 @@ const searchInputSchema = {
.string()
.optional()
.describe('Filter data tables by name (case-insensitive partial match)'),
projectId: z.string().optional().describe('Filter by project ID'),
projectId: z.string().optional(),
limit: createLimitSchema(SEARCH_MAX_RESULTS),
} satisfies z.ZodRawShape;
@ -32,8 +32,6 @@ export const createSearchDataTablesTool = (
): ToolDefinition<typeof searchInputSchema> => ({
name: 'search_data_tables',
config: {
description:
'Search for data tables accessible to the current user. Use this to find a data table ID before modifying or adding data to it.',
inputSchema: searchInputSchema,
outputSchema: searchOutputSchema,
annotations: {

View File

@ -39,7 +39,7 @@ import type { WorkflowPublishedDataService } from '@/workflows/workflow-publishe
export { type FoundWorkflow };
const inputSchema = z.object({
workflowId: z.string().describe('The ID of the workflow to execute'),
workflowId: z.string(),
executionMode: z
.enum(['manual', 'production'])
.describe(

View File

@ -15,8 +15,8 @@ import type { Telemetry } from '@/telemetry';
import type { WorkflowFinderService } from '@/workflows/workflow-finder.service';
const inputSchema = z.object({
workflowId: z.string().describe('The ID of the workflow the execution belongs to'),
executionId: z.string().describe('The ID of the execution to retrieve'),
workflowId: z.string(),
executionId: z.string(),
includeData: z
.boolean()
.optional()
@ -70,8 +70,6 @@ export const createGetExecutionTool = (
): ToolDefinition<typeof inputSchema.shape> => ({
name: 'get_execution',
config: {
description:
'Get execution details by execution ID and workflow ID. By default returns metadata only. Set includeData to true to include node execution data, optionally filtered by nodeNames and truncated by truncateData.',
inputSchema: inputSchema.shape,
outputSchema,
annotations: {

View File

@ -19,7 +19,7 @@ import { getTriggerDetails, type WebhookEndpoints } from './webhook-utils';
import { getMcpWorkflow } from './workflow-validation.utils';
const inputSchema = {
workflowId: z.string().describe('The ID of the workflow to retrieve'),
workflowId: z.string(),
} satisfies z.ZodRawShape;
export type WorkflowDetailsOutputSchema = typeof workflowDetailsOutputSchema;
@ -44,7 +44,7 @@ export const createWorkflowDetailsTool = (
return {
name: 'get_workflow_details',
config: {
description: 'Get detailed information about a specific workflow including trigger details',
description: "Get a workflow's full definition, including how to trigger it.",
inputSchema,
outputSchema,
annotations: {

View File

@ -15,7 +15,7 @@ import { getMcpWorkflow } from './workflow-validation.utils';
const MAX_RESULTS = 50;
const inputSchema = {
workflowId: z.string().describe('The ID of the workflow to read version history for'),
workflowId: z.string(),
limit: createLimitSchema(MAX_RESULTS),
offset: z
.number()
@ -66,8 +66,7 @@ export const createGetWorkflowHistoryTool = (
): ToolDefinition<typeof inputSchema> => ({
name: 'get_workflow_history',
config: {
description:
'List the saved version history of a workflow (newest first), so you can inspect how it changed over time and pick a version to retrieve or restore.',
description: "List a workflow's saved versions, newest first.",
inputSchema,
outputSchema,
annotations: {

View File

@ -21,7 +21,7 @@ import { getMcpWorkflowVersion } from './workflow-history.utils';
import { getMcpWorkflow } from './workflow-validation.utils';
const inputSchema = {
workflowId: z.string().describe('The ID of the workflow the version belongs to'),
workflowId: z.string(),
versionId: z.string().describe('The version ID to retrieve, as returned by get_workflow_history'),
} satisfies z.ZodRawShape;
@ -80,8 +80,6 @@ export const createGetWorkflowVersionTool = (
): ToolDefinition<typeof inputSchema> => ({
name: 'get_workflow_version',
config: {
description:
'Retrieve the full content (nodes, connections, node groups) of a specific workflow version from its history. Use the versionId from get_workflow_history.',
inputSchema,
outputSchema,
annotations: {

View File

@ -64,7 +64,6 @@ export const createListTagsTool = (
): ToolDefinition<typeof inputSchema> => ({
name: 'list_tags',
config: {
description: 'List all workflow tags in the instance.',
inputSchema,
outputSchema,
annotations: {

View File

@ -20,7 +20,7 @@ import type { ToolDefinition, UserCalledMCPToolEventPayload } from '../mcp.types
import { getMcpWorkflow } from './workflow-validation.utils';
const inputSchema = z.object({
workflowId: z.string().describe('The ID of the workflow to generate test pin data for'),
workflowId: z.string(),
});
const outputSchema = {

View File

@ -14,7 +14,7 @@ import type { ToolDefinition, UserCalledMCPToolEventPayload } from '../mcp.types
import { getMcpWorkflow } from './workflow-validation.utils';
const inputSchema = z.object({
workflowId: z.string().describe('The ID of the workflow to publish'),
workflowId: z.string(),
versionId: z
.string()
.optional()
@ -46,8 +46,7 @@ export const createPublishWorkflowTool = (
): ToolDefinition<typeof inputSchema.shape> => ({
name: 'publish_workflow',
config: {
description:
'Publish (activate) a workflow to make it available for production execution. This creates an active version from the current draft.',
description: 'Publish (activate) a workflow: creates an active version from the current draft.',
inputSchema: inputSchema.shape,
outputSchema,
annotations: {

View File

@ -31,9 +31,7 @@ export const workflowMetaSchema = z
.custom<WorkflowFEMeta>((_value): _value is WorkflowFEMeta => true)
.nullable();
export const dataTableColumnTypeSchema = z
.enum(['string', 'number', 'boolean', 'date'])
.describe('The data type of the column');
export const dataTableColumnTypeSchema = z.enum(['string', 'number', 'boolean', 'date']);
export const dataTableColumnSchema = z.object({
id: z.string().describe('The unique identifier of the column'),
@ -53,18 +51,9 @@ export const dataTableSchema = z.object({
columns: z.array(dataTableColumnSchema).describe('The columns defined in this data table'),
});
export const createLimitSchema = (max: number) =>
z
.number()
.int()
.positive()
.max(max)
.optional()
.describe(`Limit the number of results (max ${max})`);
export const createLimitSchema = (max: number) => z.number().int().positive().max(max).optional();
export const dataTableProjectIdSchema = z
.string()
.describe('The project ID the data table belongs to');
export const dataTableProjectIdSchema = z.string();
export const columnNameSchema = z
.string()

View File

@ -15,21 +15,10 @@ import { getMcpWorkflow } from './workflow-validation.utils';
const MAX_RESULTS = 200;
const inputSchema = {
workflowId: z.string().optional().describe('Filter executions by workflow ID'),
status: z
.array(z.enum(ExecutionStatusList))
.optional()
.describe('Filter by execution status(es)'),
startedAfter: z
.string()
.datetime({ offset: true })
.optional()
.describe('ISO 8601 timestamp — only return executions that started after this time'),
startedBefore: z
.string()
.datetime({ offset: true })
.optional()
.describe('ISO 8601 timestamp — only return executions that started before this time'),
workflowId: z.string().optional(),
status: z.array(z.enum(ExecutionStatusList)).optional(),
startedAfter: z.string().datetime({ offset: true }).optional(),
startedBefore: z.string().datetime({ offset: true }).optional(),
limit: createLimitSchema(MAX_RESULTS),
lastId: z
.string()
@ -69,8 +58,6 @@ export const createSearchExecutionsTool = (
): ToolDefinition<typeof inputSchema> => ({
name: 'search_executions',
config: {
description:
'Search for workflow executions with optional filters. Returns execution metadata including status, timing, and workflow ID.',
inputSchema,
outputSchema,
annotations: {

View File

@ -11,7 +11,7 @@ import { createLimitSchema } from './schemas';
const MAX_RESULTS = 100;
const inputSchema = {
projectId: z.string().describe('The ID of the project to search folders in'),
projectId: z.string(),
query: z.string().optional().describe('Filter folders by name (case-insensitive partial match)'),
limit: createLimitSchema(MAX_RESULTS),
} satisfies z.ZodRawShape;
@ -40,8 +40,6 @@ export const createSearchFoldersTool = (
): ToolDefinition<typeof inputSchema> => ({
name: 'search_folders',
config: {
description:
'Search for folders within a project. Use this to find a folder ID before creating a workflow in a specific folder. Requires a projectId — use search_projects first if needed.',
inputSchema,
outputSchema,
annotations: {

View File

@ -85,8 +85,6 @@ export const createSearchWorkflowsTool = (
return {
name: 'search_workflows',
config: {
description:
'Search for workflows with optional filters. Returns a preview of each workflow.',
inputSchema,
outputSchema,
annotations: {

View File

@ -31,7 +31,7 @@ import {
import { getMcpWorkflow } from './workflow-validation.utils';
const inputSchema = z.object({
workflowId: z.string().describe('The ID of the workflow to test'),
workflowId: z.string(),
pinData: z
.record(z.array(z.record(z.unknown())))
.describe(

View File

@ -14,7 +14,7 @@ import type { ToolDefinition, UserCalledMCPToolEventPayload } from '../mcp.types
import { getMcpWorkflow } from './workflow-validation.utils';
const inputSchema = z.object({
workflowId: z.string().describe('The ID of the workflow to unpublish'),
workflowId: z.string(),
});
type UnpublishWorkflowOutput = {
@ -38,8 +38,6 @@ export const createUnpublishWorkflowTool = (
): ToolDefinition<typeof inputSchema.shape> => ({
name: 'unpublish_workflow',
config: {
description:
'Unpublish (deactivate) a workflow to stop it from being available for production execution.',
inputSchema: inputSchema.shape,
outputSchema,
annotations: {

View File

@ -12,7 +12,7 @@ import type { ToolDefinition, UserCalledMCPToolEventPayload } from '../../mcp.ty
import { getMcpWorkflow } from '../workflow-validation.utils';
const inputSchema = {
workflowId: z.string().describe('The ID of the workflow to archive'),
workflowId: z.string(),
} satisfies z.ZodRawShape;
const outputSchema = {
@ -33,7 +33,6 @@ export const createArchiveWorkflowTool = (
): ToolDefinition<typeof inputSchema> => ({
name: MCP_ARCHIVE_WORKFLOW_TOOL.toolName,
config: {
description: 'Archive a workflow in n8n by its ID.',
inputSchema,
outputSchema,
annotations: {

View File

@ -16,7 +16,7 @@ import { getMcpWorkflow } from '../workflow-validation.utils';
import { buildRestoreVersionMetadata } from './version-metadata';
const inputSchema = z.object({
workflowId: z.string().describe('The ID of the workflow to restore'),
workflowId: z.string(),
versionId: z.string().describe('The version ID to restore, as returned by get_workflow_history'),
});
@ -57,7 +57,7 @@ export const createRestoreWorkflowVersionTool = (
name: 'restore_workflow_version',
config: {
description:
'Restore a workflow to a previous version from its history. Re-applies that version as the current draft and records a new history entry. Use get_workflow_history to find the versionId.',
"Restore a previous version as the workflow's current draft; records a new history entry.",
inputSchema: inputSchema.shape,
outputSchema,
annotations: {

View File

@ -269,7 +269,7 @@ function collectTouchedNodes(operations: PartialUpdateOperation[]): Map<string,
// annotation is compile-checked against it via ToolCallback's parameter types.
const buildInputSchema = (canvasGroupsEnabled: boolean) =>
({
workflowId: z.string().describe('The ID of the workflow to update.'),
workflowId: z.string(),
skillsUsed: z.array(z.string()).optional().describe(SKILLS_USED_PARAM_DESCRIPTION),
operations: z
.array(buildOperationInputSchema(canvasGroupsEnabled))