diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..3999f55 --- /dev/null +++ b/.env.example @@ -0,0 +1,11 @@ +# Qdrant Database Configuration +# Get your Qdrant instance from https://cloud.qdrant.io/ +QDRANT_URL=http://localhost:6333 +QDRANT_API_KEY=your_qdrant_api_key_here + +# Alternative environment variables (for compatibility with aldrin-labs/openSVM) +QDRANT_SERVER=http://localhost:6333 +QDRANT=your_qdrant_api_key_here + +# Node Environment +NODE_ENV=development diff --git a/NETLIFY.md b/NETLIFY.md new file mode 100644 index 0000000..2a1614d --- /dev/null +++ b/NETLIFY.md @@ -0,0 +1,211 @@ +# Netlify Deployment Guide for IDLHub + +This guide covers deploying IDLHub to Netlify with Qdrant integration for enhanced search capabilities. + +## Quick Deploy + +[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/openSVM/idlhub) + +## Prerequisites + +- Netlify account (free tier works) +- Qdrant instance (local or [Qdrant Cloud](https://cloud.qdrant.io/)) +- GitHub account (for automatic deployments) + +## Deployment Steps + +### 1. Connect Repository to Netlify + +1. Log in to [Netlify](https://app.netlify.com/) +2. Click **"Add new site"** → **"Import an existing project"** +3. Select **GitHub** and authorize Netlify +4. Choose the `openSVM/idlhub` repository +5. Configure build settings (Netlify will auto-detect `netlify.toml`) + +### 2. Configure Environment Variables + +In Netlify dashboard, go to **Site settings** → **Environment variables** and add: + +``` +QDRANT_URL=https://your-qdrant-instance.qdrant.tech:6333 +QDRANT_API_KEY=your_qdrant_api_key_here +NODE_ENV=production +``` + +**Getting Qdrant credentials:** +- Sign up at [Qdrant Cloud](https://cloud.qdrant.io/) +- Create a new cluster +- Copy the cluster URL and API key + +### 3. Deploy + +Click **"Deploy site"**. Netlify will: +- Build the site (no build step needed for static files) +- Deploy to CDN +- Set up redirects (configured in `netlify.toml`) + +### 4. Initialize Qdrant Database + +After deployment, initialize Qdrant with IDL data: + +```bash +# Clone the repository locally +git clone https://github.com/openSVM/idlhub.git +cd idlhub + +# Install dependencies +npm install + +# Set environment variables +cp .env.example .env +# Edit .env with your Qdrant credentials + +# Initialize Qdrant +npm run qdrant:init +``` + +## Netlify Configuration + +The `netlify.toml` file configures: + +### Redirects + +- `/mcp` → `/install.sh` - Installation script endpoint +- `/install` → `/install.sh` - Alternative endpoint +- `/*` → `/index.html` - SPA fallback + +### Headers + +- **Content-Type** headers for `.sh` and `.json` files +- **Security headers** (X-Frame-Options, X-XSS-Protection, etc.) +- **Cache-Control** headers for optimal performance + +## Custom Domain Setup + +1. In Netlify dashboard, go to **Domain settings** +2. Click **"Add custom domain"** +3. Enter your domain (e.g., `idlhub.com`) +4. Configure DNS: + - Add CNAME record: `your-site.netlify.app` + - Or use Netlify DNS (recommended) + +## Qdrant Integration Features + +Once Qdrant is initialized, IDLHub gains: + +- **Semantic search** for programs and protocols +- **Similar program discovery** +- **Fast metadata queries** +- **Full IDL storage** within program metadata +- **openSVM compatibility** - Uses the same ProgramMetadataEntry model + +### Qdrant Collections + +The following collections are created (matching aldrin-labs/openSVM structure): + +1. **program_metadata** - Program metadata with full IDL objects (ProgramMetadataEntry model) +2. **token_metadata** - Token metadata (for future expansion) +3. **idl_cache** - Additional IDL caching layer + +### openSVM Compatibility + +IDLHub uses the same database model as aldrin-labs/openSVM: +- **ProgramMetadataEntry** interface for all programs +- IDL stored as part of program metadata (`idl` field) +- Same collection names and structure +- Compatible search and retrieval patterns + +## Testing the Deployment + +### Test Installation Script + +```bash +curl -fsSL https://your-site.netlify.app/mcp | sh +``` + +### Test API Endpoints + +```bash +# Test main site +curl https://your-site.netlify.app/ + +# Test index.json +curl https://your-site.netlify.app/index.json + +# Test specific IDL +curl https://your-site.netlify.app/IDLs/jupiterIDL.json +``` + +## Continuous Deployment + +Netlify automatically deploys when you push to GitHub: + +1. Make changes to your repository +2. Commit and push to main branch +3. Netlify detects changes and redeploys +4. Changes are live in ~1-2 minutes + +## Performance Optimization + +Netlify provides: + +- **Global CDN** - Fast delivery worldwide +- **Automatic HTTPS** - Free SSL certificates +- **Branch deploys** - Preview deployments for PRs +- **Edge functions** - Optional serverless functions + +## Monitoring + +View deployment logs and analytics: +- **Netlify Dashboard** → Your site → **Deploys** +- **Analytics** tab for traffic insights +- **Functions** tab (if using edge functions) + +## Troubleshooting + +### Deployment Fails + +Check Netlify deploy logs for errors: +- Go to **Deploys** → Click failed deploy → View logs + +### Qdrant Connection Issues + +1. Verify environment variables are set in Netlify +2. Check Qdrant instance is running +3. Verify API key and URL are correct +4. Check Qdrant firewall allows connections + +### Redirects Not Working + +1. Verify `netlify.toml` is in repository root +2. Check redirect configuration syntax +3. Clear browser cache +4. Test with curl: `curl -I https://your-site.netlify.app/mcp` + +## Local Development + +Test Netlify configuration locally: + +```bash +# Install Netlify CLI +npm install -g netlify-cli + +# Run local dev server +netlify dev + +# Test redirects +curl http://localhost:8888/mcp +``` + +## Additional Resources + +- [Netlify Documentation](https://docs.netlify.com/) +- [Qdrant Documentation](https://qdrant.tech/documentation/) +- [IDLHub GitHub](https://github.com/openSVM/idlhub) + +## Support + +For issues with: +- **Netlify deployment**: [Netlify Support](https://answers.netlify.com/) +- **Qdrant integration**: [Qdrant Discord](https://discord.gg/qdrant) +- **IDLHub**: [GitHub Issues](https://github.com/openSVM/idlhub/issues) diff --git a/README.md b/README.md index a0fa056..66a2457 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,50 @@ npm run mcp:websocket See [mcp-server/README.md](mcp-server/README.md) for complete documentation. +## 🚀 Deployment + +### Netlify (Recommended) + +Deploy IDLHub to Netlify with one click: + +[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/openSVM/idlhub) + +IDLHub includes a complete Netlify configuration (`netlify.toml`) with: +- Automatic redirects for the `/mcp` installation endpoint +- Optimized caching and security headers +- CDN distribution for fast global access + +**Quick setup:** +1. Click the deploy button above +2. Configure environment variables for Qdrant (optional) +3. Deploy and get your custom domain + +See [NETLIFY.md](NETLIFY.md) for detailed deployment instructions. + +### Qdrant Integration + +IDLHub supports Qdrant for enhanced search and discovery: + +```bash +# Install dependencies +npm install + +# Configure Qdrant credentials +cp .env.example .env +# Edit .env with your Qdrant URL and API key + +# Initialize Qdrant collections +npm run qdrant:init +``` + +This enables: +- Semantic search across protocols +- Similar protocol discovery +- Fast metadata queries +- Search history tracking + +Qdrant configuration is based on [aldrin-labs/openSVM](https://github.com/aldrin-labs/openSVM) for compatibility. + ## 📦 Protocol Categories - **DEX**: Decentralized exchanges (Orca, Raydium, Phoenix, OpenBook) diff --git a/lib/README.md b/lib/README.md new file mode 100644 index 0000000..3d63063 --- /dev/null +++ b/lib/README.md @@ -0,0 +1,133 @@ +# IDLHub Library + +This directory contains shared utilities and type definitions for IDLHub, following the aldrin-labs/openSVM model for compatibility. + +## Structure + +``` +lib/ +├── qdrant.js # Qdrant database utilities +├── types/ # TypeScript type definitions +│ ├── program.ts # Program and IDL types (openSVM compatible) +│ └── index.ts # Type exports +└── README.md # This file +``` + +## Qdrant Integration + +### Collections + +IDLHub uses the same collection structure as aldrin-labs/openSVM: + +- **program_metadata** - Main collection storing programs with full IDL data +- **token_metadata** - Token metadata (for future expansion) +- **idl_cache** - Additional caching layer + +### ProgramMetadataEntry Model + +The core data model matches openSVM's `ProgramMetadataEntry`: + +```typescript +interface ProgramMetadataEntry { + id: string; // Program ID + programId: string; // Same as id + name: string; // Program name + description?: string; // Program description + githubUrl?: string; // GitHub repository URL + websiteUrl?: string; // Official website + docsUrl?: string; // Documentation URL + category?: string; // Category (defi, nft, etc.) + idl?: any; // Full IDL JSON object + verified: boolean; // Verification status + cached: boolean; // Cache status + lastUpdated: number; // Timestamp + cacheExpiry: number; // Cache expiration + tags?: string[]; // Tags for search +} +``` + +### Key Features + +1. **IDL Storage** - IDLs are stored as part of program metadata in the `idl` field +2. **Semantic Search** - Vector-based search for programs +3. **Cache Management** - Automatic cache expiration and refresh +4. **Batch Operations** - Efficient bulk storage and retrieval +5. **openSVM Compatibility** - Uses identical data structures + +## Usage + +### Initialize Collections + +```javascript +const qdrant = require('./lib/qdrant'); + +await qdrant.initializeCollections(); +``` + +### Store Program Metadata + +```javascript +const protocol = { + id: 'jupiter', + name: 'Jupiter', + description: 'Jupiter Aggregator', + category: 'dex-aggregator', + idlPath: 'IDLs/jupiterIDL.json', + status: 'available', + repo: 'https://github.com/jup-ag/jupiter-core' +}; + +// Stores program with IDL automatically loaded +await qdrant.storeProgramMetadata(protocol); +``` + +### Search Programs + +```javascript +// Search by text query +const results = await qdrant.searchPrograms('dex trading', 10); +``` + +### Get Program by ID + +```javascript +const program = await qdrant.getProgramMetadata('jupiter'); +console.log(program.idl); // Full IDL object +``` + +### Batch Operations + +```javascript +// Store multiple programs +await qdrant.batchStorePrograms(protocols); + +// Retrieve multiple programs +const programs = await qdrant.batchGetProgramMetadata(['jupiter', 'orca']); +``` + +## Type Definitions + +TypeScript types are available in `lib/types/`: + +```typescript +import { ProgramMetadataEntry, IDL, IDLProtocol } from './lib/types'; +``` + +These types ensure compatibility with aldrin-labs/openSVM when integrating with shared databases. + +## Environment Variables + +```bash +QDRANT_URL=http://localhost:6333 # Qdrant server URL +QDRANT_API_KEY=your_api_key # Qdrant API key +QDRANT_SERVER=http://localhost:6333 # Alternative (openSVM compatible) +QDRANT=your_api_key # Alternative (openSVM compatible) +``` + +## Compatibility + +This implementation is designed to be compatible with aldrin-labs/openSVM: +- Same collection names and structure +- Identical ProgramMetadataEntry model +- Compatible search patterns +- Shared database can be used by both projects diff --git a/lib/qdrant.js b/lib/qdrant.js new file mode 100644 index 0000000..8bac12b --- /dev/null +++ b/lib/qdrant.js @@ -0,0 +1,385 @@ +/** + * Qdrant Database utilities for IDLHub + * Based on aldrin-labs/openSVM Qdrant configuration + * Uses the same ProgramMetadataEntry model for compatibility + */ + +const { QdrantClient } = require('@qdrant/js-client-rest'); +const fs = require('fs'); +const path = require('path'); + +// Collection names - matching openSVM structure +const COLLECTIONS = { + PROGRAM_METADATA: 'program_metadata', // Main collection matching openSVM + TOKEN_METADATA: 'token_metadata', // For future token support + IDL_CACHE: 'idl_cache' // Additional caching layer +}; + +// Vector size for embeddings - matching openSVM +const VECTOR_SIZE = 384; + +// Initialize Qdrant client with timeout +const qdrantClient = new QdrantClient({ + url: process.env.QDRANT_URL || process.env.QDRANT_SERVER || 'http://localhost:6333', + apiKey: process.env.QDRANT_API_KEY || process.env.QDRANT || undefined, +}); + +// Helper function to add timeout to Qdrant operations +async function withTimeout(promise, timeoutMs = 5000) { + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => reject(new Error('Qdrant operation timed out')), timeoutMs); + }); + + return Promise.race([promise, timeoutPromise]); +} + +/** + * Generate simple embedding for text content + * This is a basic implementation - in production, use a proper embedding model + */ +function generateSimpleEmbedding(text) { + const vector = new Array(VECTOR_SIZE).fill(0); + + if (!text) return vector; + + const normalizedText = text.toLowerCase(); + + // Simple hash-based vector generation + for (let i = 0; i < VECTOR_SIZE; i++) { + const hash = normalizedText.split('').reduce((acc, char, idx) => { + return ((acc << 5) - acc + char.charCodeAt(0) + idx) & 0xffffffff; + }, i * 31); + vector[i] = (hash % 1000) / 1000; // Normalize to 0-1 + } + + return vector; +} + +/** + * Initialize Qdrant collections for IDLHub + * Uses the same structure as aldrin-labs/openSVM for compatibility + */ +async function initializeCollections() { + try { + console.log('Initializing Qdrant collections (openSVM compatible)...'); + + // Helper function to ensure collection exists + const ensureCollection = async (collectionName, description) => { + try { + const exists = await qdrantClient.getCollection(collectionName).catch(() => null); + + if (!exists) { + await qdrantClient.createCollection(collectionName, { + vectors: { + size: VECTOR_SIZE, + distance: 'Cosine' + } + }); + console.log(`✅ Created ${collectionName} collection - ${description}`); + } else { + console.log(`✓ ${collectionName} collection already exists`); + } + } catch (error) { + console.error(`Error creating ${collectionName} collection:`, error.message); + throw error; + } + }; + + // Helper function to ensure index exists + const ensureIndex = async (collectionName, fieldName, fieldType = 'keyword') => { + try { + await qdrantClient.createPayloadIndex(collectionName, { + field_name: fieldName, + field_schema: fieldType + }); + console.log(` ✓ Created index for ${fieldName} in ${collectionName}`); + } catch (error) { + if (error?.data?.status?.error?.includes('already exists') || + error?.message?.includes('already exists')) { + console.log(` ✓ Index for ${fieldName} in ${collectionName} already exists`); + } else { + console.warn(` ⚠ Failed to create index for ${fieldName}:`, error?.message); + } + } + }; + + // Create PROGRAM_METADATA collection (matching openSVM) + await ensureCollection(COLLECTIONS.PROGRAM_METADATA, 'Program metadata with IDLs (openSVM compatible)'); + await ensureIndex(COLLECTIONS.PROGRAM_METADATA, 'programId'); + await ensureIndex(COLLECTIONS.PROGRAM_METADATA, 'name'); + await ensureIndex(COLLECTIONS.PROGRAM_METADATA, 'category'); + await ensureIndex(COLLECTIONS.PROGRAM_METADATA, 'verified'); + + // Create TOKEN_METADATA collection (matching openSVM) + await ensureCollection(COLLECTIONS.TOKEN_METADATA, 'Token metadata (openSVM compatible)'); + await ensureIndex(COLLECTIONS.TOKEN_METADATA, 'mint'); + await ensureIndex(COLLECTIONS.TOKEN_METADATA, 'symbol'); + + // Create IDL_CACHE collection for additional caching + await ensureCollection(COLLECTIONS.IDL_CACHE, 'IDL content cache'); + await ensureIndex(COLLECTIONS.IDL_CACHE, 'protocolId'); + + console.log('✅ All Qdrant collections initialized successfully (openSVM compatible)'); + return true; + } catch (error) { + console.error('❌ Error initializing Qdrant collections:', error); + return false; + } +} + +/** + * Store program metadata with IDL (matching openSVM ProgramMetadataEntry model) + * @param {Object} protocol - Protocol from index.json + * @param {Object} idl - IDL JSON object + */ +async function storeProgramMetadata(protocol, idl = null) { + try { + // Load IDL if not provided + let idlData = idl; + if (!idlData && protocol.idlPath) { + try { + const idlPath = path.join(__dirname, '..', protocol.idlPath); + if (fs.existsSync(idlPath)) { + idlData = JSON.parse(fs.readFileSync(idlPath, 'utf8')); + } + } catch (error) { + console.warn(`Could not load IDL for ${protocol.id}:`, error.message); + } + } + + // Create ProgramMetadataEntry matching openSVM model + const now = Date.now(); + const CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours + + const programMetadata = { + id: protocol.id, + programId: protocol.id, + name: protocol.name, + description: protocol.description, + websiteUrl: null, // Can be extracted from repo + docsUrl: null, + githubUrl: protocol.repo, + category: protocol.category, + verified: protocol.status === 'available', + idl: idlData, // Store the full IDL + cached: true, + lastUpdated: now, + cacheExpiry: now + CACHE_DURATION, + tags: [protocol.category, protocol.status] + }; + + // Generate embedding from program content + const textContent = `${programMetadata.name} ${programMetadata.description} ${programMetadata.category} ${programMetadata.programId}`; + const vector = generateSimpleEmbedding(textContent); + + await withTimeout( + qdrantClient.upsert(COLLECTIONS.PROGRAM_METADATA, { + wait: true, + points: [{ + id: programMetadata.id, + vector, + payload: { + ...programMetadata, + lastUpdated: Number(programMetadata.lastUpdated), + cacheExpiry: Number(programMetadata.cacheExpiry) + } + }] + }) + ); + + console.log(`Stored program metadata for: ${protocol.name} (${protocol.id})`); + } catch (error) { + console.error(`Error storing program metadata for ${protocol.id}:`, error); + throw error; + } +} + +/** + * Search for programs by text query (matching openSVM search patterns) + */ +async function searchPrograms(query, limit = 10) { + try { + const vector = generateSimpleEmbedding(query); + + const searchResult = await withTimeout( + qdrantClient.search(COLLECTIONS.PROGRAM_METADATA, { + vector, + limit, + with_payload: true, + with_vector: false + }) + ); + + return searchResult.map(result => ({ + ...result.payload, + score: result.score + })); + } catch (error) { + console.error('Error searching programs:', error); + return []; + } +} + +/** + * Get program metadata by ID (matching openSVM getCachedProgramMetadata) + */ +async function getProgramMetadata(programId) { + try { + const result = await withTimeout( + qdrantClient.search(COLLECTIONS.PROGRAM_METADATA, { + vector: generateSimpleEmbedding(programId), + filter: { + must: [ + { key: 'programId', match: { value: programId } } + ] + }, + limit: 1, + with_payload: true + }) + ); + + if (result.length > 0) { + const metadata = result[0].payload; + + // Check if cache is still valid + const now = Date.now(); + if (metadata.cacheExpiry > now) { + return metadata; + } else { + console.log(`Program metadata cache expired for ${programId}`); + return null; + } + } + + return null; + } catch (error) { + console.error(`Error getting metadata for ${programId}:`, error); + return null; + } +} + +/** + * Batch store multiple programs (matching openSVM batch operations) + */ +async function batchStorePrograms(protocols) { + try { + if (!protocols || protocols.length === 0) return; + + console.log(`Batch storing ${protocols.length} programs with IDLs...`); + + const now = Date.now(); + const CACHE_DURATION = 24 * 60 * 60 * 1000; + + const points = protocols.map(protocol => { + // Try to load IDL for each protocol + let idlData = null; + if (protocol.idlPath) { + try { + const idlPath = path.join(__dirname, '..', protocol.idlPath); + if (fs.existsSync(idlPath)) { + idlData = JSON.parse(fs.readFileSync(idlPath, 'utf8')); + } + } catch (error) { + console.warn(`Could not load IDL for ${protocol.id}`); + } + } + + // Create ProgramMetadataEntry + const programMetadata = { + id: protocol.id, + programId: protocol.id, + name: protocol.name, + description: protocol.description, + githubUrl: protocol.repo, + category: protocol.category, + verified: protocol.status === 'available', + idl: idlData, + cached: true, + lastUpdated: now, + cacheExpiry: now + CACHE_DURATION, + tags: [protocol.category, protocol.status] + }; + + const textContent = `${programMetadata.name} ${programMetadata.description} ${programMetadata.category} ${programMetadata.programId}`; + const vector = generateSimpleEmbedding(textContent); + + return { + id: programMetadata.id, + vector, + payload: { + ...programMetadata, + lastUpdated: Number(programMetadata.lastUpdated), + cacheExpiry: Number(programMetadata.cacheExpiry) + } + }; + }); + + await withTimeout( + qdrantClient.upsert(COLLECTIONS.PROGRAM_METADATA, { + wait: true, + points + }), + 15000 // Longer timeout for batch operations + ); + + console.log(`✅ Successfully stored ${protocols.length} programs`); + } catch (error) { + console.error('Error batch storing programs:', error); + throw error; + } +} + +/** + * Batch get program metadata (matching openSVM batchGetCachedProgramMetadata) + */ +async function batchGetProgramMetadata(programIds) { + const results = new Map(); + + try { + const batchSize = 10; + for (let i = 0; i < programIds.length; i += batchSize) { + const batch = programIds.slice(i, i + batchSize); + + const promises = batch.map(async (programId) => { + const metadata = await getProgramMetadata(programId); + if (metadata) { + results.set(programId, metadata); + } + }); + + await Promise.all(promises); + } + + return results; + } catch (error) { + console.error('Error batch getting program metadata:', error); + return new Map(); + } +} + +/** + * Health check for Qdrant connection + */ +async function healthCheck() { + try { + await withTimeout(qdrantClient.getCollections(), 3000); + return true; + } catch (error) { + console.error('Qdrant health check failed:', error); + return false; + } +} + +module.exports = { + qdrantClient, + COLLECTIONS, + VECTOR_SIZE, + initializeCollections, + storeProgramMetadata, + searchPrograms, + getProgramMetadata, + batchStorePrograms, + batchGetProgramMetadata, + healthCheck, + generateSimpleEmbedding +}; diff --git a/lib/types/index.ts b/lib/types/index.ts new file mode 100644 index 0000000..31bd52e --- /dev/null +++ b/lib/types/index.ts @@ -0,0 +1,6 @@ +/** + * Type definitions for IDLHub + * Based on aldrin-labs/openSVM models for compatibility + */ + +export * from './program'; diff --git a/lib/types/program.ts b/lib/types/program.ts new file mode 100644 index 0000000..aba9ed5 --- /dev/null +++ b/lib/types/program.ts @@ -0,0 +1,103 @@ +/** + * Program and IDL types based on aldrin-labs/openSVM + * This ensures compatibility with the openSVM database model + */ + +/** + * Program metadata entry - matches openSVM ProgramMetadataEntry + */ +export interface ProgramMetadataEntry { + id: string; // programId + programId: string; + name: string; + description?: string; + githubUrl?: string; + twitterUrl?: string; + websiteUrl?: string; + docsUrl?: string; + logoUrl?: string; + idl?: any; // IDL JSON object + verified: boolean; + category?: string; // 'defi', 'nft', 'gaming', 'infrastructure', etc. + tags?: string[]; + deployedSlot?: number; + authority?: string; + upgradeAuthority?: string; + cached: boolean; + lastUpdated: number; + cacheExpiry: number; +} + +/** + * IDL Protocol entry from index.json + */ +export interface IDLProtocol { + id: string; + name: string; + description: string; + category: string; + idlPath: string; + repo: string | null; + status: 'available' | 'placeholder'; + version: string; + lastUpdated: string; +} + +/** + * IDL Instruction definition + */ +export interface IDLInstruction { + name: string; + accounts: Array<{ + name: string; + isMut: boolean; + isSigner: boolean; + }>; + args: Array<{ + name: string; + type: string | object; + }>; +} + +/** + * IDL Account definition + */ +export interface IDLAccount { + name: string; + type: { + kind: string; + fields?: Array<{ + name: string; + type: string | object; + }>; + }; +} + +/** + * IDL Type definition + */ +export interface IDLType { + name: string; + type: { + kind: string; + fields?: Array<{ + name: string; + type: string | object; + }>; + }; +} + +/** + * Complete IDL structure + */ +export interface IDL { + version: string; + name: string; + instructions: IDLInstruction[]; + accounts?: IDLAccount[]; + types?: IDLType[]; + metadata?: { + address?: string; + [key: string]: any; + }; +} diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 0000000..440ecb1 --- /dev/null +++ b/netlify.toml @@ -0,0 +1,62 @@ +[build] + # No build command needed for static site + publish = "." + command = "echo 'No build required'" + +# Redirect /mcp to install.sh script +[[redirects]] + from = "/mcp" + to = "/install.sh" + status = 200 + force = true + +# Additional redirects for convenience +[[redirects]] + from = "/install" + to = "/install.sh" + status = 200 + force = true + +# SPA fallback for the web interface +[[redirects]] + from = "/*" + to = "/index.html" + status = 200 + conditions = {Role = ["public"]} + +# Headers for install script +[[headers]] + for = "/install.sh" + [headers.values] + Content-Type = "text/plain; charset=utf-8" + X-Content-Type-Options = "nosniff" + Cache-Control = "public, max-age=3600" + +[[headers]] + for = "/mcp" + [headers.values] + Content-Type = "text/plain; charset=utf-8" + X-Content-Type-Options = "nosniff" + Cache-Control = "public, max-age=3600" + +# Headers for JSON files +[[headers]] + for = "/*.json" + [headers.values] + Content-Type = "application/json; charset=utf-8" + Cache-Control = "public, max-age=300" + +# Headers for security +[[headers]] + for = "/*" + [headers.values] + X-Frame-Options = "DENY" + X-XSS-Protection = "1; mode=block" + Referrer-Policy = "strict-origin-when-cross-origin" + +# Environment configuration +[context.production.environment] + NODE_VERSION = "18" + +[context.deploy-preview.environment] + NODE_VERSION = "18" diff --git a/package.json b/package.json index e2e8339..88f2574 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "test": "node mcp-server/test/basic.test.js", "mcp:stdio": "node mcp-server/src/index.js", "mcp:websocket": "node mcp-server/src/websocket-server.js", - "mcp:start": "npm run mcp:stdio" + "mcp:start": "npm run mcp:stdio", + "qdrant:init": "node scripts/init-qdrant.js" }, "bin": { "idlhub-mcp": "./mcp-server/src/index.js" @@ -23,6 +24,7 @@ "license": "ISC", "dependencies": { "@modelcontextprotocol/sdk": "^1.19.1", + "@qdrant/js-client-rest": "^1.13.0", "ws": "^8.18.3" } } diff --git a/scripts/init-qdrant.js b/scripts/init-qdrant.js new file mode 100644 index 0000000..fbf382d --- /dev/null +++ b/scripts/init-qdrant.js @@ -0,0 +1,60 @@ +#!/usr/bin/env node +/** + * Initialize Qdrant database with IDL metadata + * This script loads all protocols from index.json and stores them in Qdrant + */ + +const fs = require('fs'); +const path = require('path'); +const qdrant = require('../lib/qdrant'); + +async function initializeQdrant() { + console.log('🚀 Starting Qdrant initialization...\n'); + + try { + // Check Qdrant health + console.log('Checking Qdrant connection...'); + const isHealthy = await qdrant.healthCheck(); + + if (!isHealthy) { + console.error('❌ Qdrant is not accessible. Please check your connection settings.'); + console.log('\nMake sure:'); + console.log(' 1. Qdrant is running (docker, cloud instance, or local)'); + console.log(' 2. QDRANT_URL and QDRANT_API_KEY are set correctly in .env'); + process.exit(1); + } + + console.log('✅ Qdrant connection successful\n'); + + // Initialize collections + console.log('Creating collections...'); + await qdrant.initializeCollections(); + console.log(''); + + // Load index.json + console.log('Loading protocols from index.json...'); + const indexPath = path.join(__dirname, '..', 'index.json'); + const indexData = JSON.parse(fs.readFileSync(indexPath, 'utf8')); + + const protocols = indexData.protocols; + console.log(`Found ${protocols.length} protocols\n`); + + // Batch store protocols + console.log('Storing programs with IDLs in Qdrant...'); + await qdrant.batchStorePrograms(protocols); + + console.log('\n✅ Qdrant initialization complete!'); + console.log(`\nStored ${protocols.length} programs with IDLs in openSVM-compatible format`); + console.log(`Collections: ${Object.keys(qdrant.COLLECTIONS).join(', ')}`); + console.log('\nYou can now use Qdrant for semantic search and program discovery.'); + console.log('This database is compatible with aldrin-labs/openSVM.'); + + } catch (error) { + console.error('\n❌ Error during initialization:', error.message); + console.error('\nFull error:', error); + process.exit(1); + } +} + +// Run initialization +initializeQdrant();