diff --git a/src/filesystem/index.ts b/src/filesystem/index.ts index 9792932205..4f521aa58f 100644 --- a/src/filesystem/index.ts +++ b/src/filesystem/index.ts @@ -176,22 +176,18 @@ const readTextFileHandler = async (args: z.infer) throw new Error("Cannot specify both head and tail parameters simultaneously"); } + let content: string; if (args.tail) { - const tailContent = await tailFile(validPath, args.tail); - return { - content: [{ type: "text" as const, text: tailContent }], - }; + content = await tailFile(validPath, args.tail); + } else if (args.head) { + content = await headFile(validPath, args.head); + } else { + content = await readFileContent(validPath); } - if (args.head) { - const headContent = await headFile(validPath, args.head); - return { - content: [{ type: "text" as const, text: headContent }], - }; - } - const content = await readFileContent(validPath); return { content: [{ type: "text" as const, text: content }], + structuredContent: { content } }; }; @@ -201,12 +197,7 @@ server.registerTool( title: "Read File (Deprecated)", description: "Read the complete contents of a file as text. DEPRECATED: Use read_text_file instead.", inputSchema: ReadTextFileArgsSchema.shape, - outputSchema: { - content: z.array(z.object({ - type: z.literal("text"), - text: z.string() - })) - } + outputSchema: { content: z.string() } }, readTextFileHandler ); @@ -228,12 +219,7 @@ server.registerTool( tail: z.number().optional().describe("If provided, returns only the last N lines of the file"), head: z.number().optional().describe("If provided, returns only the first N lines of the file") }, - outputSchema: { - content: z.array(z.object({ - type: z.literal("text"), - text: z.string() - })) - } + outputSchema: { content: z.string() } }, readTextFileHandler ); @@ -281,8 +267,10 @@ server.registerTool( ? "audio" // Fallback for other binary types, not officially supported by the spec but has been used for some time : "blob"; + const contentItem = { type: type as 'image' | 'audio' | 'blob', data, mimeType }; return { - content: [{ type, data, mimeType }], + content: [contentItem], + structuredContent: { content: [contentItem] } } as unknown as CallToolResult; } ); @@ -302,12 +290,7 @@ server.registerTool( .min(1) .describe("Array of file paths to read. Each path must be a string pointing to a valid file within allowed directories.") }, - outputSchema: { - content: z.array(z.object({ - type: z.literal("text"), - text: z.string() - })) - } + outputSchema: { content: z.string() } }, async (args: z.infer) => { const results = await Promise.all( @@ -322,8 +305,10 @@ server.registerTool( } }), ); + const text = results.join("\n---\n"); return { - content: [{ type: "text" as const, text: results.join("\n---\n") }], + content: [{ type: "text" as const, text }], + structuredContent: { content: text } }; } ); @@ -340,18 +325,15 @@ server.registerTool( path: z.string(), content: z.string() }, - outputSchema: { - content: z.array(z.object({ - type: z.literal("text"), - text: z.string() - })) - } + outputSchema: { content: z.string() } }, async (args: z.infer) => { const validPath = await validatePath(args.path); await writeFileContent(validPath, args.content); + const text = `Successfully wrote to ${args.path}`; return { - content: [{ type: "text" as const, text: `Successfully wrote to ${args.path}` }], + content: [{ type: "text" as const, text }], + structuredContent: { content: text } }; } ); @@ -372,18 +354,14 @@ server.registerTool( })), dryRun: z.boolean().default(false).describe("Preview changes using git-style diff format") }, - outputSchema: { - content: z.array(z.object({ - type: z.literal("text"), - text: z.string() - })) - } + outputSchema: { content: z.string() } }, async (args: z.infer) => { const validPath = await validatePath(args.path); const result = await applyFileEdits(validPath, args.edits, args.dryRun); return { content: [{ type: "text" as const, text: result }], + structuredContent: { content: result } }; } ); @@ -400,18 +378,15 @@ server.registerTool( inputSchema: { path: z.string() }, - outputSchema: { - content: z.array(z.object({ - type: z.literal("text"), - text: z.string() - })) - } + outputSchema: { content: z.string() } }, async (args: z.infer) => { const validPath = await validatePath(args.path); await fs.mkdir(validPath, { recursive: true }); + const text = `Successfully created directory ${args.path}`; return { - content: [{ type: "text" as const, text: `Successfully created directory ${args.path}` }], + content: [{ type: "text" as const, text }], + structuredContent: { content: text } }; } ); @@ -428,12 +403,7 @@ server.registerTool( inputSchema: { path: z.string() }, - outputSchema: { - content: z.array(z.object({ - type: z.literal("text"), - text: z.string() - })) - } + outputSchema: { content: z.string() } }, async (args: z.infer) => { const validPath = await validatePath(args.path); @@ -443,6 +413,7 @@ server.registerTool( .join("\n"); return { content: [{ type: "text" as const, text: formatted }], + structuredContent: { content: formatted } }; } ); @@ -460,12 +431,7 @@ server.registerTool( path: z.string(), sortBy: z.enum(["name", "size"]).optional().default("name").describe("Sort entries by name or size") }, - outputSchema: { - content: z.array(z.object({ - type: z.literal("text"), - text: z.string() - })) - } + outputSchema: { content: z.string() } }, async (args: z.infer) => { const validPath = await validatePath(args.path); @@ -521,11 +487,11 @@ server.registerTool( `Combined size: ${formatSize(totalSize)}` ]; + const text = [...formattedEntries, ...summary].join("\n"); + const contentBlock = { type: "text" as const, text }; return { - content: [{ - type: "text" as const, - text: [...formattedEntries, ...summary].join("\n") - }], + content: [contentBlock], + structuredContent: { content: [contentBlock] } }; } ); @@ -543,12 +509,7 @@ server.registerTool( path: z.string(), excludePatterns: z.array(z.string()).optional().default([]) }, - outputSchema: { - content: z.array(z.object({ - type: z.literal("text"), - text: z.string() - })) - } + outputSchema: { content: z.string() } }, async (args: z.infer) => { interface TreeEntry { @@ -595,11 +556,11 @@ server.registerTool( } const treeData = await buildTree(rootPath, args.excludePatterns); + const text = JSON.stringify(treeData, null, 2); + const contentBlock = { type: "text" as const, text }; return { - content: [{ - type: "text" as const, - text: JSON.stringify(treeData, null, 2) - }], + content: [contentBlock], + structuredContent: { content: [contentBlock] } }; } ); @@ -617,19 +578,17 @@ server.registerTool( source: z.string(), destination: z.string() }, - outputSchema: { - content: z.array(z.object({ - type: z.literal("text"), - text: z.string() - })) - } + outputSchema: { content: z.string() } }, async (args: z.infer) => { const validSourcePath = await validatePath(args.source); const validDestPath = await validatePath(args.destination); await fs.rename(validSourcePath, validDestPath); + const text = `Successfully moved ${args.source} to ${args.destination}`; + const contentBlock = { type: "text" as const, text }; return { - content: [{ type: "text" as const, text: `Successfully moved ${args.source} to ${args.destination}` }], + content: [contentBlock], + structuredContent: { content: [contentBlock] } }; } ); @@ -649,18 +608,15 @@ server.registerTool( pattern: z.string(), excludePatterns: z.array(z.string()).optional().default([]) }, - outputSchema: { - content: z.array(z.object({ - type: z.literal("text"), - text: z.string() - })) - } + outputSchema: { content: z.string() } }, async (args: z.infer) => { const validPath = await validatePath(args.path); const results = await searchFilesWithValidation(validPath, args.pattern, allowedDirectories, { excludePatterns: args.excludePatterns }); + const text = results.length > 0 ? results.join("\n") : "No matches found"; return { - content: [{ type: "text" as const, text: results.length > 0 ? results.join("\n") : "No matches found" }], + content: [{ type: "text" as const, text }], + structuredContent: { content: text } }; } ); @@ -677,20 +633,17 @@ server.registerTool( inputSchema: { path: z.string() }, - outputSchema: { - content: z.array(z.object({ - type: z.literal("text"), - text: z.string() - })) - } + outputSchema: { content: z.string() } }, async (args: z.infer) => { const validPath = await validatePath(args.path); const info = await getFileStats(validPath); + const text = Object.entries(info) + .map(([key, value]) => `${key}: ${value}`) + .join("\n"); return { - content: [{ type: "text" as const, text: Object.entries(info) - .map(([key, value]) => `${key}: ${value}`) - .join("\n") }], + content: [{ type: "text" as const, text }], + structuredContent: { content: text } }; } ); @@ -705,19 +658,13 @@ server.registerTool( "Use this to understand which directories and their nested paths are available " + "before trying to access files.", inputSchema: {}, - outputSchema: { - content: z.array(z.object({ - type: z.literal("text"), - text: z.string() - })) - } + outputSchema: { content: z.string() } }, async () => { + const text = `Allowed directories:\n${allowedDirectories.join('\n')}`; return { - content: [{ - type: "text" as const, - text: `Allowed directories:\n${allowedDirectories.join('\n')}` - }], + content: [{ type: "text" as const, text }], + structuredContent: { content: text } }; } );