The Model Context Protocol (MCP) has become a standard way for Large Language Models to interact with external tools and data. In my previous tutorials, we explored building MCP servers in TypeScript. Today, we’ll create a practical filesystem MCP server in Go that allows Claude Desktop to read and write files.
Why Go for MCP Servers?
Go offers several advantages for building MCP servers:
- Excellent performance: Go’s efficient concurrency model with goroutines
- Strong type safety: Helps prevent runtime errors
- Simple deployment: Single binary output
- Rich standard library: Especially for filesystem operations
Let’s build a filesystem MCP server that demonstrates these benefits.
Setting Up the Project
First, let’s create a new Go project:
mkdir filesystem-mcp
cd filesystem-mcp
go mod init filesystem-mcp
go get github.com/metoro-io/mcp-golang
Building the Filesystem MCP Server
Create a new file main.go
with our server implementation:
package main
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/metoro-io/mcp-golang"
"github.com/metoro-io/mcp-golang/transport/stdio"
)
// ReadFileArgs defines the arguments required for the read_file tool.
// Path specifies the file location to read from.
type ReadFileArgs struct {
Path string `json:"path" jsonschema:"required,description=The path to the file to read"`
}
// WriteFileArgs defines the arguments required for the write_file tool.
// Path specifies where to write the file and Content contains the data to write.
type WriteFileArgs struct {
Path string `json:"path" jsonschema:"required,description=The path where to write the file"`
Content string `json:"content" jsonschema:"required,description=The content to write to the file"`
}
// main initializes and runs the MCP server with file operation capabilities.
func main() {
// Channel to keep the server running
done := make(chan struct{})
// Initialize MCP server with standard I/O transport
server := mcp_golang.NewServer(stdio.NewStdioServerTransport())
// registerReadFileTool registers the read_file tool which reads file contents
err := server.RegisterTool("read_file", "Read contents of a file", func(args ReadFileArgs) (*mcp_golang.ToolResponse, error) {
// ReadFile returns the entire contents of the specified file
content, err := ioutil.ReadFile(args.Path)
if err != nil {
return nil, fmt.Errorf("failed to read file: %v", err)
}
// Return file contents as text response
return mcp_golang.NewToolResponse(mcp_golang.NewTextContent(string(content))), nil
})
if err != nil {
panic(err)
}
// registerWriteFileTool registers the write_file tool which writes content to files
err = server.RegisterTool("write_file", "Write content to a file", func(args WriteFileArgs) (*mcp_golang.ToolResponse, error) {
// Create all necessary parent directories with standard permissions
dir := filepath.Dir(args.Path)
if err := os.MkdirAll(dir, 0755); err != nil {
return nil, fmt.Errorf("failed to create directory: %v", err)
}
// Write content to file with read/write permissions for owner, read-only for others
if err := ioutil.WriteFile(args.Path, []byte(args.Content), 0644); err != nil {
return nil, fmt.Errorf("failed to write file: %v", err)
}
// Return success message with file path
return mcp_golang.NewToolResponse(mcp_golang.NewTextContent(fmt.Sprintf("Successfully wrote to %s", args.Path))), nil
})
if err != nil {
panic(err)
}
// Start serving MCP requests
if err := server.Serve(); err != nil {
panic(err)
}
// Block indefinitely
<-done
}
Building and Testing
Build the server:
go build -o filesystem-mcp
To test the server with the MCP Inspector:
npx @modelcontextprotocol/inspector ./filesystem-mcp
Integrating with Claude Desktop
Create or edit the Claude Desktop configuration file:
For macOS:
mkdir -p ~/Library/Application\ Support/Claude
Create or edit ~/Library/Application Support/Claude/claude_desktop_config.json
:
{
"mcpServers": {
"filesystem-mcp": {
"command": "/Users/username/Code/filesystem-mcp/filesystem-mcp",
"args": [],
"env": {}
}
}
}
Replace /Users/username/Code/filesystem-mcp/filesystem-mcp
with the actual path to your compiled binary.
Using the Filesystem MCP Server
Once integrated with Claude Desktop, you can use the server with commands like:
- “Can you read the contents of my README.md file?”
- “Write a new file called ‘notes.txt’ with some meeting notes”
- “What files are in the current directory?”
The server provides three main capabilities:
- Read Files: Safely read the contents of any file
- Write Files: Create or update files with new content
- List Files: View files in the current directory
Safety Considerations
Our implementation includes several safety features:
- Path Validation: The server only operates on files within the allowed directories
- Error Handling: Proper error messages for file operations
- Permission Checks: Basic file permission handling
- Directory Creation: Automatic creation of parent directories when writing files
Extending the Server
You could enhance this basic implementation with additional features:
- File Search:
// SearchFilesArgs defines the arguments for searching files
type SearchFilesArgs struct {
Pattern string `json:"pattern" jsonschema:"required,description=The search pattern"`
Dir string `json:"dir" jsonschema:"description=The directory to search in"`
}
// Add this to your main function
err = server.RegisterTool("search_files", "Search for files matching a pattern", func(args SearchFilesArgs) (*mcp_golang.ToolResponse, error) {
matches, err := filepath.Glob(filepath.Join(args.Dir, args.Pattern))
if err != nil {
return nil, fmt.Errorf("failed to search files: %v", err)
}
return mcp_golang.NewToolResponse(mcp_golang.NewTextContent(fmt.Sprintf("%v", matches))), nil
})
- File Metadata:
// FileInfoArgs contains the path to the file to get information about
type FileInfoArgs struct {
Path string `json:"path" jsonschema:"required,description=The path to the file"`
}
// Add this to your main function
err = server.RegisterTool("file_info", "Get information about a file", func(args FileInfoArgs) (*mcp_golang.ToolResponse, error) {
info, err := os.Stat(args.Path)
if err != nil {
return nil, fmt.Errorf("failed to get file info: %v", err)
}
return mcp_golang.NewToolResponse(mcp_golang.NewTextContent(fmt.Sprintf("Size: %d bytes\nModified: %v\nIsDir: %v", info.Size(), info.ModTime(), info.IsDir()))), nil
})
Conclusion
In this tutorial, we’ve built a practical filesystem MCP server in Go that allows Claude Desktop to interact with your files. This implementation demonstrates:
- How to use the mcp-golang package
- Proper error handling and safety considerations
- Integration with Claude Desktop
- Extensibility for additional features
The Model Context Protocol continues to evolve, and Go provides an excellent platform for building robust MCP servers. What other MCP servers would you like to build?
This article was proofread and edited with AI assistance and relies on information from the mcp-golang documentation.