mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-24 10:40:07 +08:00
* refactor: rewrite filesystem MCP server with new tool set - Replace existing filesystem MCP with modular architecture - Implement 6 new tools: glob, ls, grep, read, write, delete - Add comprehensive TypeScript types and Zod schemas - Maintain security with path validation and allowed directories - Improve error handling and user feedback - Add result limits for performance (100 files/matches max) - Format output with clear, helpful messages - Keep backward compatibility with existing import patterns BREAKING CHANGE: Tools renamed from snake_case to lowercase - read_file → read - write_file → write - list_directory → ls - search_files → glob - New tools: grep, delete - Removed: edit_file, create_directory, directory_tree, move_file, get_file_info * 🐛 fix: remove filesystem allowed directories restriction * 🐛 fix: relax binary detection for text files * ✨ feat: add edit tool with fuzzy matching to filesystem MCP server - Add edit tool with 9 fallback replacers from opencode for robust string replacement (SimpleReplacer, LineTrimmedReplacer, BlockAnchorReplacer, WhitespaceNormalizedReplacer, etc.) - Add Levenshtein distance algorithm for similarity matching - Improve descriptions for all tools (read, write, glob, grep, ls, delete) following opencode patterns for better LLM guidance - Register edit tool in server and export from tools index * ♻️ refactor: replace allowedDirectories with baseDir in filesystem MCP server - Change server to use single baseDir (from WORKSPACE_ROOT env or userData/workspace default) - Remove list_allowed_directories tool as restriction mechanism is removed - Add ripgrep integration for faster grep searches with JS fallback - Simplify validatePath() by removing allowlist checks - Display paths relative to baseDir in tool outputs * 📝 docs: standardize filesystem MCP server tool descriptions - Unify description format to bullet-point style across all tools - Add absolute path requirement to ls, glob, grep schemas and descriptions - Update glob and grep to output absolute paths instead of relative paths - Add missing error case documentation for edit tool (old_string === new_string) - Standardize optional path parameter descriptions * ♻️ refactor: use ripgrep for glob tool and extract shared utilities - Extract shared ripgrep utilities (runRipgrep, getRipgrepAddonPath) to types.ts - Rewrite glob tool to use `rg --files --glob` for reliable file matching - Update grep tool to import shared ripgrep utilities * 🐛 fix: handle ripgrep exit code 2 with valid results in glob tool - Process ripgrep stdout when content exists, regardless of exit code - Exit code 2 can indicate partial errors while still returning valid results - Remove fallback directory listing (had buggy regex for root-level files) - Update tool description to clarify patterns without "/" match at any depth * 🔥 chore: remove filesystem.ts.backup file Remove unnecessary backup file from mcpServers directory * 🐛 fix: use correct default workspace path in filesystem MCP server Change default baseDir from userData/workspace to userData/Data/Workspace to match the app's data storage convention (Data/Files, Data/Notes, etc.) Addresses PR #11937 review feedback. * 🐛 fix: pass WORKSPACE_ROOT to FileSystemServer constructor The envs object passed to createInMemoryMCPServer was not being used for the filesystem server. Now WORKSPACE_ROOT is passed as a constructor parameter, following the same pattern as other MCP servers. * \feat: add link to documentation for MCP server configuration requirement Wrap the configuration requirement tag in a link to the documentation for better user guidance on MCP server settings. --------- Co-authored-by: kangfenmao <kangfenmao@qq.com>
119 lines
3.0 KiB
TypeScript
119 lines
3.0 KiB
TypeScript
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
|
|
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'
|
|
import { app } from 'electron'
|
|
import fs from 'fs/promises'
|
|
import path from 'path'
|
|
|
|
import {
|
|
deleteToolDefinition,
|
|
editToolDefinition,
|
|
globToolDefinition,
|
|
grepToolDefinition,
|
|
handleDeleteTool,
|
|
handleEditTool,
|
|
handleGlobTool,
|
|
handleGrepTool,
|
|
handleLsTool,
|
|
handleReadTool,
|
|
handleWriteTool,
|
|
lsToolDefinition,
|
|
readToolDefinition,
|
|
writeToolDefinition
|
|
} from './tools'
|
|
import { logger } from './types'
|
|
|
|
export class FileSystemServer {
|
|
public server: Server
|
|
private baseDir: string
|
|
|
|
constructor(baseDir?: string) {
|
|
if (baseDir && path.isAbsolute(baseDir)) {
|
|
this.baseDir = baseDir
|
|
logger.info(`Using provided baseDir for filesystem MCP: ${baseDir}`)
|
|
} else {
|
|
const userData = app.getPath('userData')
|
|
this.baseDir = path.join(userData, 'Data', 'Workspace')
|
|
logger.info(`Using default workspace for filesystem MCP baseDir: ${this.baseDir}`)
|
|
}
|
|
|
|
this.server = new Server(
|
|
{
|
|
name: 'filesystem-server',
|
|
version: '2.0.0'
|
|
},
|
|
{
|
|
capabilities: {
|
|
tools: {}
|
|
}
|
|
}
|
|
)
|
|
|
|
this.initialize()
|
|
}
|
|
|
|
async initialize() {
|
|
try {
|
|
await fs.mkdir(this.baseDir, { recursive: true })
|
|
} catch (error) {
|
|
logger.error('Failed to create filesystem MCP baseDir', { error, baseDir: this.baseDir })
|
|
}
|
|
|
|
// Register tool list handler
|
|
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
return {
|
|
tools: [
|
|
globToolDefinition,
|
|
lsToolDefinition,
|
|
grepToolDefinition,
|
|
readToolDefinition,
|
|
editToolDefinition,
|
|
writeToolDefinition,
|
|
deleteToolDefinition
|
|
]
|
|
}
|
|
})
|
|
|
|
// Register tool call handler
|
|
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
try {
|
|
const { name, arguments: args } = request.params
|
|
|
|
switch (name) {
|
|
case 'glob':
|
|
return await handleGlobTool(args, this.baseDir)
|
|
|
|
case 'ls':
|
|
return await handleLsTool(args, this.baseDir)
|
|
|
|
case 'grep':
|
|
return await handleGrepTool(args, this.baseDir)
|
|
|
|
case 'read':
|
|
return await handleReadTool(args, this.baseDir)
|
|
|
|
case 'edit':
|
|
return await handleEditTool(args, this.baseDir)
|
|
|
|
case 'write':
|
|
return await handleWriteTool(args, this.baseDir)
|
|
|
|
case 'delete':
|
|
return await handleDeleteTool(args, this.baseDir)
|
|
|
|
default:
|
|
throw new Error(`Unknown tool: ${name}`)
|
|
}
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
logger.error(`Tool execution error for ${request.params.name}:`, { error })
|
|
return {
|
|
content: [{ type: 'text', text: `Error: ${errorMessage}` }],
|
|
isError: true
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
export default FileSystemServer
|