From dd6925895a6194b26160ba8a324b79f91a98cd4b Mon Sep 17 00:00:00 2001 From: ykshah1309 Date: Wed, 8 Apr 2026 18:23:23 -0400 Subject: [PATCH] feat(filesystem): add idempotentHint and openWorldHint annotations Add idempotentHint: true to all 10 read-only tools (reads are inherently safe to retry) and openWorldHint: false to all 14 tools (all operations are scoped to allowed directories with no external side effects). Closes #3402 --- src/filesystem/index.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/filesystem/index.ts b/src/filesystem/index.ts index 7b67e63e58..c805821069 100644 --- a/src/filesystem/index.ts +++ b/src/filesystem/index.ts @@ -217,7 +217,7 @@ server.registerTool( description: "Read the complete contents of a file as text. DEPRECATED: Use read_text_file instead.", inputSchema: ReadTextFileArgsSchema.shape, outputSchema: { content: z.string() }, - annotations: { readOnlyHint: true } + annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false } }, readTextFileHandler ); @@ -240,7 +240,7 @@ server.registerTool( head: z.number().optional().describe("If provided, returns only the first N lines of the file") }, outputSchema: { content: z.string() }, - annotations: { readOnlyHint: true } + annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false } }, readTextFileHandler ); @@ -262,7 +262,7 @@ server.registerTool( mimeType: z.string() })) }, - annotations: { readOnlyHint: true } + annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false } }, async (args: z.infer) => { const validPath = await validatePath(args.path); @@ -313,7 +313,7 @@ server.registerTool( .describe("Array of file paths to read. Each path must be a string pointing to a valid file within allowed directories.") }, outputSchema: { content: z.string() }, - annotations: { readOnlyHint: true } + annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false } }, async (args: z.infer) => { const results = await Promise.all( @@ -349,7 +349,7 @@ server.registerTool( content: z.string() }, outputSchema: { content: z.string() }, - annotations: { readOnlyHint: false, idempotentHint: true, destructiveHint: true } + annotations: { readOnlyHint: false, idempotentHint: true, destructiveHint: true, openWorldHint: false } }, async (args: z.infer) => { const validPath = await validatePath(args.path); @@ -379,7 +379,7 @@ server.registerTool( dryRun: z.boolean().default(false).describe("Preview changes using git-style diff format") }, outputSchema: { content: z.string() }, - annotations: { readOnlyHint: false, idempotentHint: false, destructiveHint: true } + annotations: { readOnlyHint: false, idempotentHint: false, destructiveHint: true, openWorldHint: false } }, async (args: z.infer) => { const validPath = await validatePath(args.path); @@ -404,7 +404,7 @@ server.registerTool( path: z.string() }, outputSchema: { content: z.string() }, - annotations: { readOnlyHint: false, idempotentHint: true, destructiveHint: false } + annotations: { readOnlyHint: false, idempotentHint: true, destructiveHint: false, openWorldHint: false } }, async (args: z.infer) => { const validPath = await validatePath(args.path); @@ -430,7 +430,7 @@ server.registerTool( path: z.string() }, outputSchema: { content: z.string() }, - annotations: { readOnlyHint: true } + annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false } }, async (args: z.infer) => { const validPath = await validatePath(args.path); @@ -459,7 +459,7 @@ server.registerTool( sortBy: z.enum(["name", "size"]).optional().default("name").describe("Sort entries by name or size") }, outputSchema: { content: z.string() }, - annotations: { readOnlyHint: true } + annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false } }, async (args: z.infer) => { const validPath = await validatePath(args.path); @@ -538,7 +538,7 @@ server.registerTool( excludePatterns: z.array(z.string()).optional().default([]) }, outputSchema: { content: z.string() }, - annotations: { readOnlyHint: true } + annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false } }, async (args: z.infer) => { interface TreeEntry { @@ -608,7 +608,7 @@ server.registerTool( destination: z.string() }, outputSchema: { content: z.string() }, - annotations: { readOnlyHint: false, idempotentHint: false, destructiveHint: true } + annotations: { readOnlyHint: false, idempotentHint: false, destructiveHint: true, openWorldHint: false } }, async (args: z.infer) => { const validSourcePath = await validatePath(args.source); @@ -639,7 +639,7 @@ server.registerTool( excludePatterns: z.array(z.string()).optional().default([]) }, outputSchema: { content: z.string() }, - annotations: { readOnlyHint: true } + annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false } }, async (args: z.infer) => { const validPath = await validatePath(args.path); @@ -665,7 +665,7 @@ server.registerTool( path: z.string() }, outputSchema: { content: z.string() }, - annotations: { readOnlyHint: true } + annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false } }, async (args: z.infer) => { const validPath = await validatePath(args.path); @@ -691,7 +691,7 @@ server.registerTool( "before trying to access files.", inputSchema: {}, outputSchema: { content: z.string() }, - annotations: { readOnlyHint: true } + annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false } }, async () => { const text = `Allowed directories:\n${allowedDirectories.join('\n')}`;