Commit 2f82819
Changed files (1)
pkg
filesystem
pkg/filesystem/server.go
@@ -1,6 +1,7 @@
package filesystem
import (
+ "encoding/json"
"fmt"
"os"
"path/filepath"
@@ -41,6 +42,9 @@ func New(allowedDirs []string) *Server {
fsServer.registerPrompts()
fsServer.registerResources()
fsServer.registerRoots()
+
+ // Set up custom resource handling to use our dynamic discovery
+ fsServer.setupResourceHandling()
return fsServer
}
@@ -151,6 +155,223 @@ func (fs *Server) registerRoots() {
}
}
+// setupResourceHandling configures custom resource handling for dynamic file discovery
+func (fs *Server) setupResourceHandling() {
+ // Use reflection to access the private createSuccessResponse method
+ // Custom handler that calls our ListResources method
+ customListResourcesHandler := func(req mcp.JSONRPCRequest) mcp.JSONRPCResponse {
+ resources := fs.ListResources()
+ result := mcp.ListResourcesResult{Resources: resources}
+
+ // Create JSON response manually since we can't access private method
+ id := req.ID
+ bytes, _ := json.Marshal(result)
+ rawMsg := json.RawMessage(bytes)
+ resultBytes := &rawMsg
+
+ return mcp.JSONRPCResponse{
+ JSONRPC: "2.0",
+ ID: id,
+ Result: resultBytes,
+ }
+ }
+
+ handlers := map[string]func(mcp.JSONRPCRequest) mcp.JSONRPCResponse{
+ "resources/list": customListResourcesHandler,
+ }
+ fs.SetCustomRequestHandler(handlers)
+}
+
+// ListResources returns all available file resources in allowed directories
+func (fs *Server) ListResources() []mcp.Resource {
+ var resources []mcp.Resource
+
+ // Include the base directory resources from parent
+ parentResources := fs.Server.ListResources()
+ resources = append(resources, parentResources...)
+
+ // Discover programming files in each allowed directory
+ for _, dir := range fs.allowedDirectories {
+ fileResources := fs.discoverProgrammingFiles(dir)
+ resources = append(resources, fileResources...)
+ }
+
+ return resources
+}
+
+// discoverProgrammingFiles finds programming-related files in a directory
+func (fs *Server) discoverProgrammingFiles(dirPath string) []mcp.Resource {
+ var resources []mcp.Resource
+
+ // Programming file extensions to include
+ programmingExts := map[string]string{
+ ".go": "text/x-go",
+ ".js": "text/javascript",
+ ".ts": "text/typescript",
+ ".py": "text/x-python",
+ ".rs": "text/x-rust",
+ ".java": "text/x-java",
+ ".c": "text/x-c",
+ ".cpp": "text/x-c++",
+ ".h": "text/x-c",
+ ".hpp": "text/x-c++",
+ ".cs": "text/x-csharp",
+ ".php": "text/x-php",
+ ".rb": "text/x-ruby",
+ ".swift": "text/x-swift",
+ ".kt": "text/x-kotlin",
+ ".scala": "text/x-scala",
+ ".sh": "text/x-shellscript",
+ ".bash": "text/x-shellscript",
+ ".zsh": "text/x-shellscript",
+ ".fish": "text/x-shellscript",
+ ".ps1": "text/x-powershell",
+ ".sql": "text/x-sql",
+ ".md": "text/markdown",
+ ".txt": "text/plain",
+ ".json": "application/json",
+ ".xml": "application/xml",
+ ".yaml": "application/x-yaml",
+ ".yml": "application/x-yaml",
+ ".toml": "application/toml",
+ ".ini": "text/plain",
+ ".cfg": "text/plain",
+ ".conf": "text/plain",
+ ".config": "application/json",
+ ".html": "text/html",
+ ".htm": "text/html",
+ ".css": "text/css",
+ ".scss": "text/x-scss",
+ ".sass": "text/x-sass",
+ ".less": "text/x-less",
+ ".vue": "text/x-vue",
+ ".jsx": "text/jsx",
+ ".tsx": "text/tsx",
+ ".svelte": "text/x-svelte",
+ ".dockerfile": "text/x-dockerfile",
+ ".gitignore": "text/plain",
+ ".gitattributes": "text/plain",
+ ".editorconfig": "text/plain",
+ ".env": "text/plain",
+ }
+
+ // Special filenames to include (without extension)
+ specialFiles := map[string]string{
+ "Makefile": "text/x-makefile",
+ "makefile": "text/x-makefile",
+ "Dockerfile": "text/x-dockerfile",
+ "docker-compose.yml": "application/x-yaml",
+ "docker-compose.yaml": "application/x-yaml",
+ "README": "text/plain",
+ "LICENSE": "text/plain",
+ "CHANGELOG": "text/plain",
+ "AUTHORS": "text/plain",
+ "CONTRIBUTORS": "text/plain",
+ "COPYING": "text/plain",
+ "INSTALL": "text/plain",
+ "NEWS": "text/plain",
+ "TODO": "text/plain",
+ "package.json": "application/json",
+ "package-lock.json": "application/json",
+ "yarn.lock": "text/plain",
+ "Cargo.toml": "application/toml",
+ "Cargo.lock": "application/toml",
+ "go.mod": "text/x-go-mod",
+ "go.sum": "text/plain",
+ "requirements.txt": "text/plain",
+ "setup.py": "text/x-python",
+ "pyproject.toml": "application/toml",
+ "pom.xml": "application/xml",
+ "build.gradle": "text/x-gradle",
+ "CMakeLists.txt": "text/x-cmake",
+ ".travis.yml": "application/x-yaml",
+ ".github": "text/plain",
+ "tsconfig.json": "application/json",
+ "webpack.config.js": "text/javascript",
+ "rollup.config.js": "text/javascript",
+ "vite.config.js": "text/javascript",
+ "jest.config.js": "text/javascript",
+ "eslint.config.js": "text/javascript",
+ ".eslintrc": "application/json",
+ ".prettierrc": "application/json",
+ }
+
+ // Walk through directory tree (limit to prevent performance issues)
+ const maxFiles = 500
+ fileCount := 0
+
+ err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
+ if err != nil || fileCount >= maxFiles {
+ return filepath.SkipDir
+ }
+
+ // Skip hidden files and directories
+ if strings.HasPrefix(info.Name(), ".") && info.Name() != ".gitignore" &&
+ info.Name() != ".gitattributes" && info.Name() != ".editorconfig" &&
+ info.Name() != ".env" && info.Name() != ".eslintrc" && info.Name() != ".prettierrc" {
+ if info.IsDir() {
+ return filepath.SkipDir
+ }
+ return nil
+ }
+
+ // Skip common non-programming directories
+ if info.IsDir() {
+ dirName := strings.ToLower(info.Name())
+ skipDirs := []string{"node_modules", "vendor", "target", "build", "dist",
+ ".git", ".svn", ".hg", "__pycache__", ".pytest_cache",
+ "coverage", ".coverage", ".nyc_output", "logs"}
+ for _, skip := range skipDirs {
+ if dirName == skip {
+ return filepath.SkipDir
+ }
+ }
+ return nil
+ }
+
+ // Check if it's a programming file
+ fileName := info.Name()
+ ext := strings.ToLower(filepath.Ext(fileName))
+
+ var mimeType string
+ var isProgFile bool
+
+ // Check by extension
+ if mimeType, isProgFile = programmingExts[ext]; isProgFile {
+ // Found by extension
+ } else if mimeType, isProgFile = specialFiles[fileName]; isProgFile {
+ // Found by special filename
+ } else if mimeType, isProgFile = specialFiles[strings.ToLower(fileName)]; isProgFile {
+ // Found by special filename (case insensitive)
+ } else {
+ return nil // Not a programming file
+ }
+
+ // Create resource for this file
+ fileURI := "file://" + path
+ relPath, _ := filepath.Rel(dirPath, path)
+
+ resource := mcp.Resource{
+ URI: fileURI,
+ Name: relPath,
+ Description: fmt.Sprintf("Programming file: %s", relPath),
+ MimeType: mimeType,
+ }
+
+ resources = append(resources, resource)
+ fileCount++
+
+ return nil
+ })
+
+ if err != nil {
+ // Log error but don't fail completely
+ fmt.Printf("Warning: Error discovering files in %s: %v\n", dirPath, err)
+ }
+
+ return resources
+}
+
// HandleFileResource handles file:// resource requests
func (fs *Server) HandleFileResource(req mcp.ReadResourceRequest) (mcp.ReadResourceResult, error) {
// Extract file path from file:// URI