Commit 4eec1d0
Changed files (1)
pkg
filesystem
pkg/filesystem/server.go
@@ -36,9 +36,10 @@ func New(allowedDirs []string) *Server {
allowedDirectories: normalizedDirs,
}
- // Register all filesystem tools and prompts
+ // Register all filesystem tools, prompts, and resources
fsServer.registerTools()
fsServer.registerPrompts()
+ fsServer.registerResources()
return fsServer
}
@@ -86,6 +87,157 @@ func (fs *Server) registerPrompts() {
fs.RegisterPrompt(editFilePrompt, fs.HandleEditFilePrompt)
}
+// registerResources registers filesystem resources (file:// URIs)
+func (fs *Server) registerResources() {
+ // Register resources for all files in allowed directories
+ // This provides discovery of available files as resources
+ for _, dir := range fs.allowedDirectories {
+ fs.discoverFilesInDirectory(dir)
+ }
+}
+
+// discoverFilesInDirectory recursively discovers files and registers them as resources
+func (fs *Server) discoverFilesInDirectory(dirPath string) {
+ filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return nil // Skip files with errors
+ }
+
+ // Skip directories
+ if info.IsDir() {
+ // Skip .git and other hidden directories
+ if strings.HasPrefix(info.Name(), ".") {
+ return filepath.SkipDir
+ }
+ return nil
+ }
+
+ // Skip hidden files and certain file types
+ if strings.HasPrefix(info.Name(), ".") {
+ return nil
+ }
+
+ // Skip files in .git directories (additional safety)
+ if strings.Contains(path, "/.git/") {
+ return nil
+ }
+
+ // Skip binary files and very large files
+ if info.Size() > 10*1024*1024 { // 10MB limit
+ return nil
+ }
+
+ // Create file:// URI
+ fileURI := "file://" + path
+
+ // Determine MIME type based on extension
+ mimeType := getMimeTypeFromPath(path)
+
+ // Create resource definition
+ resource := mcp.Resource{
+ URI: fileURI,
+ Name: info.Name(),
+ Description: fmt.Sprintf("File: %s (%d bytes)", path, info.Size()),
+ MimeType: mimeType,
+ }
+
+ // Register resource with handler
+ fs.RegisterResourceWithDefinition(resource, fs.HandleFileResource)
+
+ return nil
+ })
+}
+
+// HandleFileResource handles file:// resource requests
+func (fs *Server) HandleFileResource(req mcp.ReadResourceRequest) (mcp.ReadResourceResult, error) {
+ // Extract file path from file:// URI
+ if !strings.HasPrefix(req.URI, "file://") {
+ return mcp.ReadResourceResult{}, fmt.Errorf("invalid file URI: %s", req.URI)
+ }
+
+ filePath := req.URI[7:] // Remove "file://" prefix
+
+ // Validate path is within allowed directories
+ validPath, err := fs.validatePath(filePath)
+ if err != nil {
+ return mcp.ReadResourceResult{}, fmt.Errorf("access denied: %v", err)
+ }
+
+ // Read file content
+ content, err := os.ReadFile(validPath)
+ if err != nil {
+ return mcp.ReadResourceResult{}, fmt.Errorf("failed to read file: %v", err)
+ }
+
+ // Determine if content is binary or text
+ if isBinaryContent(content) {
+ // For binary files, return base64 encoded content
+ return mcp.ReadResourceResult{
+ Contents: []mcp.Content{
+ mcp.TextContent{
+ Type: "text",
+ Text: fmt.Sprintf("Binary file (size: %d bytes). Content not displayed.", len(content)),
+ },
+ },
+ }, nil
+ }
+
+ // For text files, return the content
+ return mcp.ReadResourceResult{
+ Contents: []mcp.Content{
+ mcp.NewTextContent(string(content)),
+ },
+ }, nil
+}
+
+// Helper function to determine MIME type from file path
+func getMimeTypeFromPath(path string) string {
+ ext := strings.ToLower(filepath.Ext(path))
+ switch ext {
+ case ".txt":
+ return "text/plain"
+ case ".md":
+ return "text/markdown"
+ case ".go":
+ return "text/x-go"
+ case ".js":
+ return "text/javascript"
+ case ".ts":
+ return "text/typescript"
+ case ".py":
+ return "text/x-python"
+ case ".json":
+ return "application/json"
+ case ".xml":
+ return "application/xml"
+ case ".html", ".htm":
+ return "text/html"
+ case ".css":
+ return "text/css"
+ case ".yaml", ".yml":
+ return "application/x-yaml"
+ default:
+ return "text/plain"
+ }
+}
+
+// Helper function to detect binary content
+func isBinaryContent(content []byte) bool {
+ // Check first 512 bytes for null bytes (common in binary files)
+ checkBytes := content
+ if len(content) > 512 {
+ checkBytes = content[:512]
+ }
+
+ for _, b := range checkBytes {
+ if b == 0 {
+ return true
+ }
+ }
+
+ return false
+}
+
// ListTools returns all available Filesystem tools
func (fs *Server) ListTools() []mcp.Tool {
return []mcp.Tool{