Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 57 additions & 110 deletions src/filesystem/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,22 +176,18 @@ const readTextFileHandler = async (args: z.infer<typeof ReadTextFileArgsSchema>)
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 }
};
};

Expand All @@ -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
);
Expand All @@ -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
);
Expand Down Expand Up @@ -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;
}
);
Expand All @@ -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<typeof ReadMultipleFilesArgsSchema>) => {
const results = await Promise.all(
Expand 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 }
};
}
);
Expand All @@ -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<typeof WriteFileArgsSchema>) => {
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 }
};
}
);
Expand All @@ -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<typeof EditFileArgsSchema>) => {
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 }
};
}
);
Expand All @@ -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<typeof CreateDirectoryArgsSchema>) => {
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 }
};
}
);
Expand All @@ -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<typeof ListDirectoryArgsSchema>) => {
const validPath = await validatePath(args.path);
Expand All @@ -443,6 +413,7 @@ server.registerTool(
.join("\n");
return {
content: [{ type: "text" as const, text: formatted }],
structuredContent: { content: formatted }
};
}
);
Expand All @@ -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<typeof ListDirectoryWithSizesArgsSchema>) => {
const validPath = await validatePath(args.path);
Expand Down Expand Up @@ -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] }
};
}
);
Expand All @@ -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<typeof DirectoryTreeArgsSchema>) => {
interface TreeEntry {
Expand Down Expand Up @@ -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] }
};
}
);
Expand All @@ -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<typeof MoveFileArgsSchema>) => {
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] }
};
}
);
Expand All @@ -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<typeof SearchFilesArgsSchema>) => {
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 }
};
}
);
Expand All @@ -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<typeof GetFileInfoArgsSchema>) => {
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 }
};
}
);
Expand All @@ -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 }
};
}
);
Expand Down