commit a179b1085439bda97127025cb436451e7c68c4c4 Author: Erik Brakke Date: Tue Jul 1 14:06:57 2025 -0600 Initial commit: Flipside Intelligence MCP Extension - Add Go-based MCP proxy for Flipside Intelligence blockchain analytics - Configure project structure with proper Go module naming - Include comprehensive README focused on product vision - Add manifest.json for Claude Desktop integration - Setup build automation with Makefile and test scripts - Configure .gitignore for Go project best practices πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bee33f2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,56 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work +go.work.sum + +# Build artifacts +dist/ +build/ +bin/ + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Environment files +.env +.env.local +.env.*.local + +# Log files +*.log + +# Temporary files +tmp/ +temp/ + +# Binary output +remote-mcp-proxy +flipside-mcp-extension \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..df2cab8 --- /dev/null +++ b/Makefile @@ -0,0 +1,167 @@ +.PHONY: build build-all clean test dxt dist deps help + +# Default target +help: + @echo "Available targets:" + @echo " build - Build for current platform" + @echo " build-all - Build for all supported platforms" + @echo " dxt - Create DXT packages using official dxt CLI (recommended)" + @echo " dist - Create legacy zip-based packages" + @echo " test - Test the binary" + @echo " clean - Clean build artifacts" + @echo " deps - Install dependencies" + @echo " help - Show this help message" + +# Default build for current platform +build: + go build -ldflags="-s -w" -o remote-mcp-proxy . + chmod +x remote-mcp-proxy + +# Build for all supported platforms +build-all: clean + # macOS + GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -o dist/remote-mcp-proxy-darwin-amd64 . + chmod +x dist/remote-mcp-proxy-darwin-amd64 + GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -o dist/remote-mcp-proxy-darwin-arm64 . + chmod +x dist/remote-mcp-proxy-darwin-arm64 + + # Linux + GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o dist/remote-mcp-proxy-linux-amd64 . + chmod +x dist/remote-mcp-proxy-linux-amd64 + GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o dist/remote-mcp-proxy-linux-arm64 . + chmod +x dist/remote-mcp-proxy-linux-arm64 + + # Windows + GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o dist/remote-mcp-proxy-windows-amd64.exe . + chmod +x dist/remote-mcp-proxy-windows-amd64.exe + GOOS=windows GOARCH=arm64 go build -ldflags="-s -w" -o dist/remote-mcp-proxy-windows-arm64.exe . + chmod +x dist/remote-mcp-proxy-windows-arm64.exe + +# Create DXT packages using official dxt CLI +dxt: build-all + mkdir -p dist/dxt-staging + + # macOS x64 + mkdir -p dist/dxt-staging/darwin-amd64 + cp dist/remote-mcp-proxy-darwin-amd64 dist/dxt-staging/darwin-amd64/remote-mcp-proxy + chmod +x dist/dxt-staging/darwin-amd64/remote-mcp-proxy + @echo "Verifying permissions for darwin-amd64:" + @ls -la dist/dxt-staging/darwin-amd64/remote-mcp-proxy + cp manifest.json dist/dxt-staging/darwin-amd64/ + cp README.md dist/dxt-staging/darwin-amd64/ 2>/dev/null || true + cd dist/dxt-staging/darwin-amd64 && npx @anthropic-ai/dxt pack . ../flipside-remote-mcp-proxy-darwin-amd64.dxt + + # macOS ARM64 + mkdir -p dist/dxt-staging/darwin-arm64 + cp dist/remote-mcp-proxy-darwin-arm64 dist/dxt-staging/darwin-arm64/remote-mcp-proxy + chmod +x dist/dxt-staging/darwin-arm64/remote-mcp-proxy + @echo "Verifying permissions for darwin-arm64:" + @ls -la dist/dxt-staging/darwin-arm64/remote-mcp-proxy + cp manifest.json dist/dxt-staging/darwin-arm64/ + cp README.md dist/dxt-staging/darwin-arm64/ 2>/dev/null || true + cd dist/dxt-staging/darwin-arm64 && npx @anthropic-ai/dxt pack . ../flipside-remote-mcp-proxy-darwin-arm64.dxt + + # Linux x64 + mkdir -p dist/dxt-staging/linux-amd64 + cp dist/remote-mcp-proxy-linux-amd64 dist/dxt-staging/linux-amd64/remote-mcp-proxy + chmod +x dist/dxt-staging/linux-amd64/remote-mcp-proxy + cp manifest.json dist/dxt-staging/linux-amd64/ + cp README.md dist/dxt-staging/linux-amd64/ 2>/dev/null || true + cd dist/dxt-staging/linux-amd64 && npx @anthropic-ai/dxt pack . ../flipside-remote-mcp-proxy-linux-amd64.dxt + + # Linux ARM64 + mkdir -p dist/dxt-staging/linux-arm64 + cp dist/remote-mcp-proxy-linux-arm64 dist/dxt-staging/linux-arm64/remote-mcp-proxy + chmod +x dist/dxt-staging/linux-arm64/remote-mcp-proxy + cp manifest.json dist/dxt-staging/linux-arm64/ + cp README.md dist/dxt-staging/linux-arm64/ 2>/dev/null || true + cd dist/dxt-staging/linux-arm64 && npx @anthropic-ai/dxt pack . ../flipside-remote-mcp-proxy-linux-arm64.dxt + + # Windows x64 + mkdir -p dist/dxt-staging/windows-amd64 + cp dist/remote-mcp-proxy-windows-amd64.exe dist/dxt-staging/windows-amd64/remote-mcp-proxy.exe + chmod +x dist/dxt-staging/windows-amd64/remote-mcp-proxy.exe + cp manifest.json dist/dxt-staging/windows-amd64/ + cp README.md dist/dxt-staging/windows-amd64/ 2>/dev/null || true + cd dist/dxt-staging/windows-amd64 && npx @anthropic-ai/dxt pack . ../flipside-remote-mcp-proxy-windows-amd64.dxt + + # Windows ARM64 + mkdir -p dist/dxt-staging/windows-arm64 + cp dist/remote-mcp-proxy-windows-arm64.exe dist/dxt-staging/windows-arm64/remote-mcp-proxy.exe + chmod +x dist/dxt-staging/windows-arm64/remote-mcp-proxy.exe + cp manifest.json dist/dxt-staging/windows-arm64/ + cp README.md dist/dxt-staging/windows-arm64/ 2>/dev/null || true + cd dist/dxt-staging/windows-arm64 && npx @anthropic-ai/dxt pack . ../flipside-remote-mcp-proxy-windows-arm64.dxt + + # Move final DXT files to dist root + mv dist/dxt-staging/*.dxt dist/ + + @echo "DXT packages created:" + @ls -la dist/*.dxt + + @echo "" + @echo "Alternative: Create tar.gz packages that preserve permissions:" + @cd dist/dxt-staging/darwin-amd64 && tar -czf ../../flipside-remote-mcp-proxy-darwin-amd64.tar.gz . + @cd dist/dxt-staging/darwin-arm64 && tar -czf ../../flipside-remote-mcp-proxy-darwin-arm64.tar.gz . + @ls -la dist/*.tar.gz + +# Legacy zip-based distribution (kept for compatibility) +dist: build-all + mkdir -p dist/dxt + + # macOS x64 + mkdir -p dist/dxt/flipside-remote-mcp-proxy-darwin-amd64 + cp dist/remote-mcp-proxy-darwin-amd64 dist/dxt/flipside-remote-mcp-proxy-darwin-amd64/remote-mcp-proxy + cp manifest.json dist/dxt/flipside-remote-mcp-proxy-darwin-amd64/ + cp README.md dist/dxt/flipside-remote-mcp-proxy-darwin-amd64/ 2>/dev/null || true + cd dist/dxt && zip -r flipside-remote-mcp-proxy-darwin-amd64.dxt flipside-remote-mcp-proxy-darwin-amd64/ + + # macOS ARM64 + mkdir -p dist/dxt/flipside-remote-mcp-proxy-darwin-arm64 + cp dist/remote-mcp-proxy-darwin-arm64 dist/dxt/flipside-remote-mcp-proxy-darwin-arm64/remote-mcp-proxy + cp manifest.json dist/dxt/flipside-remote-mcp-proxy-darwin-arm64/ + cp README.md dist/dxt/flipside-remote-mcp-proxy-darwin-arm64/ 2>/dev/null || true + cd dist/dxt && zip -r flipside-remote-mcp-proxy-darwin-arm64.dxt flipside-remote-mcp-proxy-darwin-arm64/ + + # Linux x64 + mkdir -p dist/dxt/flipside-remote-mcp-proxy-linux-amd64 + cp dist/remote-mcp-proxy-linux-amd64 dist/dxt/flipside-remote-mcp-proxy-linux-amd64/remote-mcp-proxy + cp manifest.json dist/dxt/flipside-remote-mcp-proxy-linux-amd64/ + cp README.md dist/dxt/flipside-remote-mcp-proxy-linux-amd64/ 2>/dev/null || true + cd dist/dxt && zip -r flipside-remote-mcp-proxy-linux-amd64.dxt flipside-remote-mcp-proxy-linux-amd64/ + + # Linux ARM64 + mkdir -p dist/dxt/flipside-remote-mcp-proxy-linux-arm64 + cp dist/remote-mcp-proxy-linux-arm64 dist/dxt/flipside-remote-mcp-proxy-linux-arm64/remote-mcp-proxy + cp manifest.json dist/dxt/flipside-remote-mcp-proxy-linux-arm64/ + cp README.md dist/dxt/flipside-remote-mcp-proxy-linux-arm64/ 2>/dev/null || true + cd dist/dxt && zip -r flipside-remote-mcp-proxy-linux-arm64.dxt flipside-remote-mcp-proxy-linux-arm64/ + + # Windows x64 + mkdir -p dist/dxt/flipside-remote-mcp-proxy-windows-amd64 + cp dist/remote-mcp-proxy-windows-amd64.exe dist/dxt/flipside-remote-mcp-proxy-windows-amd64/remote-mcp-proxy.exe + cp manifest.json dist/dxt/flipside-remote-mcp-proxy-windows-amd64/ + cp README.md dist/dxt/flipside-remote-mcp-proxy-windows-amd64/ 2>/dev/null || true + cd dist/dxt && zip -r flipside-remote-mcp-proxy-windows-amd64.dxt flipside-remote-mcp-proxy-windows-amd64/ + + # Windows ARM64 + mkdir -p dist/dxt/flipside-remote-mcp-proxy-windows-arm64 + cp dist/remote-mcp-proxy-windows-arm64.exe dist/dxt/flipside-remote-mcp-proxy-windows-arm64/remote-mcp-proxy.exe + cp manifest.json dist/dxt/flipside-remote-mcp-proxy-windows-arm64/ + cp README.md dist/dxt/flipside-remote-mcp-proxy-windows-arm64/ 2>/dev/null || true + cd dist/dxt && zip -r flipside-remote-mcp-proxy-windows-arm64.dxt flipside-remote-mcp-proxy-windows-arm64/ + +# Test the binary +test: build + @echo "Testing binary..." + @echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | MCP_REMOTE_URL=http://localhost:8080 ./remote-mcp-proxy + +# Clean build artifacts +clean: + rm -rf dist/ + rm -f remote-mcp-proxy remote-mcp-proxy.exe + +# Install dependencies +deps: + go mod tidy + go mod download \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3ee6b95 --- /dev/null +++ b/README.md @@ -0,0 +1,171 @@ +# Flipside Intelligence via MCP + +## What it is +An AI agent that acts as your personal blockchain data scientist, accessible directly through Claude Desktop via Model Context Protocol (MCP). + +## How it works +Natural language interface to Flipside's curated blockchain datasets and embedded analytical workflows. Simply ask questions in plain English and get sophisticated blockchain analytics instantly. + +## Key capabilities +- **Complex Analysis Made Simple**: Execute cohort analysis, user journey mapping, and trend detection through conversational queries +- **Comprehensive Data Access**: Query data across 30+ blockchain networks with billions of indexed transactions +- **Proprietary Insights**: Access Flipside's specialized scoring algorithms and predictive metrics +- **Domain Expertise**: Leverage years of blockchain growth intelligence embedded in AI workflows + +## The Vision +Democratize blockchain data science expertise. For the first time, any stakeholderβ€”from ecosystem CEOs to community managersβ€”can conduct sophisticated blockchain analysis without years of technical training. + +## Integration +Available through Claude Desktop's MCP integration, bringing enterprise-grade blockchain analytics directly into your workflow without switching tools or platforms. + +--- + +*Flipside Intelligence transforms specialized data science expertise into accessible AI-powered tools, making blockchain growth insights available to everyone.* + +## Technical Features + +- **Zero Dependencies**: Self-contained Go binary with no runtime requirements +- **Cross-Platform**: Supports macOS, Linux, and Windows (x64 and ARM64) +- **Secure Connection**: Environment-based configuration with encrypted API key authentication +- **Real-time Analytics**: Direct access to live blockchain data streams +- **Optimized Performance**: Lightweight proxy design for seamless Claude Desktop integration + +## Configuration + +The proxy is configured via environment variables: + +| Variable | Required | Description | Example | +|----------|----------|-------------|---------| +| `MCP_REMOTE_URL` | Yes | Flipside Intelligence MCP endpoint | `https://mcp.flipsidecrypto.xyz/beta/sse` | +| `FLIPSIDE_API_KEY` | Yes | Your Flipside API key for authenticated access | `your-api-key-here` | +| `MCP_DEBUG` | No | Enable debug logging | `true` or `false` | + +## Installation + +### As a Claude Desktop Extension (Recommended) + +1. Download the appropriate `.dxt` file for your platform from releases +2. Import the DXT file into Claude Desktop +3. Configure your Flipside API key and MCP endpoint in the extension settings +4. Start analyzing blockchain data with natural language queries in Claude Desktop + +### Manual Installation + +1. Download the binary for your platform: + - macOS (Intel): `remote-mcp-proxy-darwin-amd64` + - macOS (Apple Silicon): `remote-mcp-proxy-darwin-arm64` + - Linux (x64): `remote-mcp-proxy-linux-amd64` + - Linux (ARM64): `remote-mcp-proxy-linux-arm64` + - Windows (x64): `remote-mcp-proxy-windows-amd64.exe` + - Windows (ARM64): `remote-mcp-proxy-windows-arm64.exe` + +2. Make executable (Unix-like systems): + ```bash + chmod +x remote-mcp-proxy-* + ``` + +3. Configure environment variables: + ```bash + export MCP_REMOTE_URL="https://mcp.flipsidecrypto.xyz/beta/sse" + export FLIPSIDE_API_KEY="your-api-key-here" + export MCP_DEBUG="false" # Optional + ``` + +4. Run the proxy: + ```bash + ./remote-mcp-proxy-darwin-amd64 + ``` + +## Usage Examples + +Once connected through Claude Desktop, you can ask natural language questions like: + +- "Show me the top 10 DeFi protocols by TVL on Ethereum this week" +- "Analyze user retention patterns for Uniswap v3 over the last 6 months" +- "What's the correlation between gas prices and DEX trading volume?" +- "Create a cohort analysis of new wallet addresses on Polygon" + +The extension handles all the technical complexity, translating your questions into sophisticated blockchain queries and returning actionable insights. + +## Getting Your API Key + +To access Flipside Intelligence: + +1. Visit [flipsidecrypto.xyz](https://flipsidecrypto.xyz) +2. Create an account or sign in +3. Navigate to your API settings to generate your key +4. Use this key in the extension configuration + +## Development + +### Building from Source + +```bash +# Install dependencies +make deps + +# Build for current platform +make build + +# Build for all platforms +make build-all + +# Create DXT packages using official dxt CLI (recommended) +make dxt + +# Create legacy zip-based packages +make dist +``` + +**Note**: The `make dxt` command requires Node.js and uses `npx @anthropic-ai/dxt` to create properly formatted DXT packages. This is the recommended approach for creating packages compatible with Claude Desktop. + +### Project Structure + +``` +pkg/remote-mcp-proxy/ +β”œβ”€β”€ main.go # Main proxy implementation +β”œβ”€β”€ manifest.json # DXT manifest file +β”œβ”€β”€ Makefile # Build automation +β”œβ”€β”€ README.md # This file +β”œβ”€β”€ go.mod # Go module definition +└── dist/ # Build artifacts (created by make) +``` + +### Dependencies + +- [mark3labs/mcp-go](https://github.com/mark3labs/mcp-go) - MCP protocol implementation + +## Security Considerations + +- The proxy forwards all requests to the configured remote URL +- Ensure your remote MCP server implements proper authentication and authorization +- Use HTTPS URLs for production deployments +- Consider network timeouts and rate limiting on the server side + +## Troubleshooting + +### Enable Debug Logging + +Set `MCP_DEBUG=true` to see detailed request/response logs: + +```bash +MCP_DEBUG=true MCP_REMOTE_URL=https://mcp.flipsidecrypto.xyz/beta/sse FLIPSIDE_API_KEY=your-key ./flipside-mcp-extension +``` + +### Common Issues + +1. **Authentication failed**: Verify your Flipside API key is correct and active +2. **Connection refused**: Check your internet connection and firewall settings +3. **Timeout errors**: Large queries may take time; try breaking them into smaller requests +4. **Rate limiting**: Ensure you're within your API plan limits + +## License + +MIT License - see the project root for details. + +## Support + +For issues and questions: +- GitHub Issues: [flipside-org/flipside-mcp-extension](https://github.com/flipside-org/flipside-mcp-extension/issues) +- Documentation: [docs.flipsidecrypto.xyz](https://docs.flipsidecrypto.xyz) +- Email: support@flipside.xyz \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2455a96 --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module github.com/flipside-org/flipside-mcp-extension + +go 1.24.3 + +require github.com/mark3labs/mcp-go v0.32.0 + +require ( + github.com/google/uuid v1.6.0 // indirect + github.com/spf13/cast v1.7.1 // indirect + github.com/yosida95/uritemplate/v3 v3.0.2 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a735303 --- /dev/null +++ b/go.sum @@ -0,0 +1,26 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mark3labs/mcp-go v0.32.0 h1:fgwmbfL2gbd67obg57OfV2Dnrhs1HtSdlY/i5fn7MU8= +github.com/mark3labs/mcp-go v0.32.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= +github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..ff570d9 --- /dev/null +++ b/main.go @@ -0,0 +1,286 @@ +package main + +import ( + "context" + "fmt" + "io" + "log" + "net/url" + "os" + "strconv" + "strings" + "time" + + "github.com/mark3labs/mcp-go/client" + "github.com/mark3labs/mcp-go/client/transport" + "github.com/mark3labs/mcp-go/mcp" + "github.com/mark3labs/mcp-go/server" +) + +type MCPProxy struct { + remoteURL string + apiKey string + debug bool + logger *log.Logger + client *client.Client + server *server.MCPServer +} + +func NewMCPProxy(remoteURL, apiKey string, debug bool) *MCPProxy { + logger := log.New(os.Stderr, "[MCP-PROXY] ", log.LstdFlags) + if !debug { + logger.SetOutput(io.Discard) + } + + // Convert /sse to /mcp if needed + if strings.Contains(remoteURL, "/sse") { + remoteURL = strings.Replace(remoteURL, "/sse", "/mcp", 1) + logger.Printf("Converted SSE URL to MCP URL: %s", remoteURL) + } + + return &MCPProxy{ + remoteURL: remoteURL, + apiKey: apiKey, + debug: debug, + logger: logger, + } +} + +func (p *MCPProxy) createRemoteClient() error { + p.logger.Printf("Creating remote MCP client for: %s", p.remoteURL) + + // Parse URL and add API key as query parameter + parsedURL, err := url.Parse(p.remoteURL) + if err != nil { + return fmt.Errorf("failed to parse remote URL: %w", err) + } + + query := parsedURL.Query() + query.Set("apiKey", p.apiKey) + parsedURL.RawQuery = query.Encode() + finalURL := parsedURL.String() + + p.logger.Printf("Final remote URL: %s", finalURL) + + // Create headers with API key + headers := map[string]string{ + "Authorization": "Bearer " + p.apiKey, + "User-Agent": "flipside-mcp-proxy/1.0", + } + + // Create streamable HTTP client options + options := []transport.StreamableHTTPCOption{ + transport.WithHTTPHeaders(headers), + transport.WithHTTPTimeout(30 * time.Second), + } + + // Create the MCP client + mcpClient, err := client.NewStreamableHttpClient(finalURL, options...) + if err != nil { + return fmt.Errorf("failed to create MCP client: %w", err) + } + + p.client = mcpClient + return nil +} + +func (p *MCPProxy) setupProxyServer(ctx context.Context) error { + p.logger.Printf("Setting up MCP proxy server") + + // Create MCP server + mcpServer := server.NewMCPServer("flipside-remote-mcp-proxy", "1.0.0") + + // Initialize remote client first + p.logger.Printf("Initializing remote client...") + _, err := p.client.Initialize(ctx, mcp.InitializeRequest{ + Params: mcp.InitializeParams{ + ProtocolVersion: "2024-11-05", + Capabilities: mcp.ClientCapabilities{}, + ClientInfo: mcp.Implementation{ + Name: "flipside-remote-mcp-proxy", + Version: "1.0.0", + }, + }, + }) + if err != nil { + return fmt.Errorf("failed to initialize remote client: %w", err) + } + + p.logger.Printf("Remote client initialized successfully") + + // Get tools from remote and add them to proxy server + if err := p.addRemoteToolsToServer(ctx, mcpServer); err != nil { + return fmt.Errorf("failed to add remote tools: %w", err) + } + + // Try to add resources and prompts (may not be supported) + p.addRemoteResourcesToServer(ctx, mcpServer) + p.addRemotePromptsToServer(ctx, mcpServer) + + p.server = mcpServer + return nil +} + +func (p *MCPProxy) addRemoteToolsToServer(ctx context.Context, mcpServer *server.MCPServer) error { + p.logger.Printf("Adding remote tools to proxy server") + + // List tools from remote client + toolsResponse, err := p.client.ListTools(ctx, mcp.ListToolsRequest{}) + if err != nil { + p.logger.Printf("Failed to list remote tools: %v", err) + return err + } + + p.logger.Printf("Found %d remote tools", len(toolsResponse.Tools)) + + // Add each tool to the proxy server + for _, tool := range toolsResponse.Tools { + p.logger.Printf("Adding tool: %s", tool.Name) + + // Create a closure to capture the tool + currentTool := tool + + mcpServer.AddTool(currentTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + p.logger.Printf("Proxying tool call: %s", currentTool.Name) + + // Forward the tool call to the remote client + response, err := p.client.CallTool(ctx, mcp.CallToolRequest{ + Params: mcp.CallToolParams{ + Name: request.Params.Name, + Arguments: request.Params.Arguments, + }, + }) + + if err != nil { + p.logger.Printf("Error calling remote tool %s: %v", currentTool.Name, err) + return nil, err + } + + return response, nil + }) + } + + return nil +} + +func (p *MCPProxy) addRemoteResourcesToServer(ctx context.Context, mcpServer *server.MCPServer) { + p.logger.Printf("Adding remote resources to proxy server") + + // List resources from remote client + resourcesResponse, err := p.client.ListResources(ctx, mcp.ListResourcesRequest{}) + if err != nil { + p.logger.Printf("Remote server doesn't support resources: %v", err) + return + } + + p.logger.Printf("Found %d remote resources", len(resourcesResponse.Resources)) + + // Add each resource to the proxy server + for _, resource := range resourcesResponse.Resources { + p.logger.Printf("Adding resource: %s", resource.URI) + + mcpServer.AddResource(resource, func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) { + p.logger.Printf("Proxying read resource: %s", request.Params.URI) + + response, err := p.client.ReadResource(ctx, request) + if err != nil { + return nil, err + } + + return response.Contents, nil + }) + } +} + +func (p *MCPProxy) addRemotePromptsToServer(ctx context.Context, mcpServer *server.MCPServer) { + p.logger.Printf("Adding remote prompts to proxy server") + + // List prompts from remote client + promptsResponse, err := p.client.ListPrompts(ctx, mcp.ListPromptsRequest{}) + if err != nil { + p.logger.Printf("Remote server doesn't support prompts: %v", err) + return + } + + p.logger.Printf("Found %d remote prompts", len(promptsResponse.Prompts)) + + // Add each prompt to the proxy server + for _, prompt := range promptsResponse.Prompts { + p.logger.Printf("Adding prompt: %s", prompt.Name) + + currentPrompt := prompt + + mcpServer.AddPrompt(currentPrompt, func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) { + p.logger.Printf("Proxying get prompt: %s", request.Params.Name) + + response, err := p.client.GetPrompt(ctx, request) + if err != nil { + return nil, err + } + + return response, nil + }) + } +} + +func (p *MCPProxy) run() error { + ctx := context.Background() + + // Create remote client + if err := p.createRemoteClient(); err != nil { + return fmt.Errorf("failed to create remote client: %w", err) + } + + // Setup proxy server (this initializes the remote client and discovers tools) + if err := p.setupProxyServer(ctx); err != nil { + return fmt.Errorf("failed to setup proxy server: %w", err) + } + + p.logger.Printf("Starting MCP proxy server with stdio transport") + p.logger.Printf("Remote URL: %s", p.remoteURL) + p.logger.Printf("Debug mode: %v", p.debug) + + // Start the proxy server with stdio transport + if err := server.ServeStdio(p.server); err != nil { + return fmt.Errorf("server error: %w", err) + } + + return nil +} + +func maskAPIKey(apiKey string) string { + if len(apiKey) <= 8 { + return "***" + } + return apiKey[:4] + "..." + apiKey[len(apiKey)-4:] +} + +func main() { + // Always enable initial logging to stderr for startup diagnostics + startupLogger := log.New(os.Stderr, "[MCP-PROXY-STARTUP] ", log.LstdFlags) + startupLogger.Println("Starting Flipside MCP Remote Proxy...") + + remoteURL := os.Getenv("MCP_REMOTE_URL") + if remoteURL == "" { + startupLogger.Fatal("ERROR: MCP_REMOTE_URL environment variable is required") + } + startupLogger.Printf("Remote URL configured: %s", remoteURL) + + apiKey := os.Getenv("FLIPSIDE_API_KEY") + if apiKey == "" { + startupLogger.Fatal("ERROR: FLIPSIDE_API_KEY environment variable is required") + } + startupLogger.Printf("API key configured: %s", maskAPIKey(apiKey)) + + debugStr := os.Getenv("MCP_DEBUG") + debug, _ := strconv.ParseBool(debugStr) + startupLogger.Printf("Debug mode: %v", debug) + + proxy := NewMCPProxy(remoteURL, apiKey, debug) + + startupLogger.Println("Starting MCP proxy with mcp-go client/server architecture...") + + if err := proxy.run(); err != nil { + startupLogger.Fatalf("Proxy error: %v", err) + } +} \ No newline at end of file diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..88c89bc --- /dev/null +++ b/manifest.json @@ -0,0 +1,59 @@ +{ + "dxt_version": "1.0", + "name": "flipside-intelligence-mcp", + "version": "1.0.0", + "display_name": "Flipside Intelligence", + "description": "Your personal blockchain data scientist via MCP", + "long_description": "AI-powered blockchain analytics accessible through Claude Desktop. Execute sophisticated blockchain analysis with natural language queries - from cohort analysis to trend detection across 30+ networks with billions of indexed transactions.", + "author": { + "name": "Flipside", + "email": "support@flipside.xyz", + "url": "https://flipside.xyz" + }, + "repository": { + "type": "git", + "url": "https://github.com/flipside-org/flipside-mcp-extension" + }, + "homepage": "https://flipside.xyz", + "license": "MIT", + "keywords": ["blockchain", "analytics", "data-science", "mcp", "flipside", "defi", "crypto", "intelligence"], + "server": { + "type": "binary", + "entry_point": "${__dirname}/remote-mcp-proxy", + "mcp_config": { + "command": "${__dirname}/remote-mcp-proxy", + "env": { + "MCP_REMOTE_URL": "${user_config.remote_url}", + "MCP_DEBUG": "${user_config.debug}", + "FLIPSIDE_API_KEY": "${user_config.api_key}" + } + } + }, + "user_config": { + "remote_url": { + "type": "string", + "title": "Flipside Intelligence Endpoint", + "description": "The MCP endpoint for Flipside Intelligence services", + "required": true, + "default": "https://mcp.flipsidecrypto.xyz/beta/sse" + }, + "api_key": { + "type": "string", + "title": "Flipside API Key", + "description": "Your Flipside API key for authenticated access", + "required": true, + "sensitive": true + }, + "debug": { + "type": "boolean", + "title": "Enable Debug Mode", + "description": "Enable verbose logging for troubleshooting queries and connections", + "required": false, + "default": false + } + }, + "compatibility": { + "platforms": ["darwin", "linux", "win32"], + "architectures": ["amd64", "arm64"] + } +} diff --git a/test-interactive.sh b/test-interactive.sh new file mode 100755 index 0000000..addbe35 --- /dev/null +++ b/test-interactive.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# Interactive test script for MCP remote proxy +# Starts the proxy and lets you send JSON-RPC messages manually + +set -e + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +# Configuration +REMOTE_URL="${MCP_REMOTE_URL:-https://mcp.flipsidecrypto.xyz/beta/sse}" +API_KEY="${FLIPSIDE_API_KEY}" +DEBUG="${MCP_DEBUG:-true}" + +if [ -z "$API_KEY" ]; then + echo -e "${RED}Error: FLIPSIDE_API_KEY environment variable is required${NC}" + echo "Usage: FLIPSIDE_API_KEY=your_key ./test-interactive.sh" + exit 1 +fi + +# Build if needed +if [ ! -f "./remote-mcp-proxy" ]; then + echo -e "${YELLOW}Building proxy binary...${NC}" + go build -o remote-mcp-proxy . +fi + +echo -e "${BLUE}πŸ”§ Starting MCP Remote Proxy in interactive mode${NC}" +echo -e "${BLUE}Remote URL: ${REMOTE_URL}${NC}" +echo -e "${BLUE}Debug Mode: ${DEBUG}${NC}" +echo "" +echo -e "${YELLOW}πŸ’‘ Usage:${NC}" +echo -e " β€’ Type JSON-RPC messages and press Enter" +echo -e " β€’ Try: {\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\"}" +echo -e " β€’ Press Ctrl+C to exit" +echo "" +echo -e "${GREEN}πŸš€ Proxy is ready! Enter JSON-RPC messages:${NC}" +echo "" + +# Start the proxy with environment variables +exec env MCP_REMOTE_URL="$REMOTE_URL" FLIPSIDE_API_KEY="$API_KEY" MCP_DEBUG="$DEBUG" ./remote-mcp-proxy \ No newline at end of file diff --git a/test-proxy.sh b/test-proxy.sh new file mode 100755 index 0000000..d516bba --- /dev/null +++ b/test-proxy.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +# Test script for the MCP remote proxy +# This simulates how Claude Desktop would interact with the proxy + +set -e + +# Colors for output +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Configuration +REMOTE_URL="${MCP_REMOTE_URL:-https://mcp.flipsidecrypto.xyz/beta/sse}" +API_KEY="${FLIPSIDE_API_KEY}" +DEBUG="${MCP_DEBUG:-true}" + +if [ -z "$API_KEY" ]; then + echo "Error: FLIPSIDE_API_KEY environment variable is required" + echo "Usage: FLIPSIDE_API_KEY=your_key ./test-proxy.sh" + exit 1 +fi + +echo -e "${BLUE}πŸ§ͺ Testing MCP Remote Proxy${NC}" +echo -e "${BLUE}Remote URL: ${REMOTE_URL}${NC}" +echo -e "${BLUE}Debug Mode: ${DEBUG}${NC}" +echo -e "${BLUE}API Key: ${API_KEY:0:8}...${NC}" +echo "" + +# Build the proxy if it doesn't exist +if [ ! -f "./remote-mcp-proxy" ]; then + echo -e "${YELLOW}Building proxy binary...${NC}" + go build -o remote-mcp-proxy . +fi + +# Function to test a message +test_message() { + local message="$1" + local description="$2" + + echo -e "${YELLOW}πŸ“€ Testing: ${description}${NC}" + echo -e "${BLUE}Message: ${message}${NC}" + echo -e "${GREEN}Response (press Ctrl+C when done):${NC}" + echo "" + + echo "$message" | MCP_REMOTE_URL="$REMOTE_URL" FLIPSIDE_API_KEY="$API_KEY" MCP_DEBUG="$DEBUG" ./remote-mcp-proxy + + echo "" + echo -e "${BLUE}────────────────────────────────────${NC}" + echo "" +} + +echo -e "${GREEN}πŸš€ Starting proxy tests...${NC}" +echo "" + +# Test 1: Initialize +echo "TEST 1: Initialize" +read -p "Press Enter to continue or Ctrl+C to skip..." +test_message '{ + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "protocolVersion": "2024-11-05", + "capabilities": {}, + "clientInfo": { + "name": "test-client", + "version": "1.0.0" + } + } +}' "Initialize" + +# Test 2: List Tools +echo "TEST 2: List Tools" +read -p "Press Enter to continue or Ctrl+C to skip..." +test_message '{ + "jsonrpc": "2.0", + "id": 2, + "method": "tools/list" +}' "List Tools" + +echo -e "${GREEN}βœ… All tests completed!${NC}" \ No newline at end of file diff --git a/test-simple.sh b/test-simple.sh new file mode 100755 index 0000000..aa2d065 --- /dev/null +++ b/test-simple.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# Simple one-shot test for MCP remote proxy + +if [ -z "$FLIPSIDE_API_KEY" ]; then + echo "Error: FLIPSIDE_API_KEY required" + exit 1 +fi + +# Build if needed +[ ! -f "./remote-mcp-proxy" ] && go build -o remote-mcp-proxy . + +echo "Testing tools/list..." +echo "Press Ctrl+C after you see the response..." +echo "" + +# Use a pipe to send the message +echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | \ + MCP_REMOTE_URL="${MCP_REMOTE_URL:-https://mcp.flipsidecrypto.xyz/beta/sse}" \ + FLIPSIDE_API_KEY="$FLIPSIDE_API_KEY" \ + MCP_DEBUG="${MCP_DEBUG:-true}" \ + ./remote-mcp-proxy \ No newline at end of file