Fix critical duplicate method handlers bug, WebSocket compilation issues, CI pipeline errors, missing RPC methods, and benchmark performance #11
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: MCP Inspector Compatibility Test | |
| on: | |
| push: | |
| branches: [ main, develop ] | |
| pull_request: | |
| branches: [ main, develop ] | |
| workflow_dispatch: | |
| env: | |
| CARGO_TERM_COLOR: always | |
| RUST_BACKTRACE: 1 | |
| NODE_VERSION: '18' | |
| INSPECTOR_VERSION: '0.16.2' | |
| jobs: | |
| mcp-inspector-test: | |
| name: Test MCP Inspector Compatibility | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: stable | |
| - name: Install Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| # Use the recommended action for caching Rust dependencies | |
| - name: Cache Rust dependencies | |
| uses: Swatinem/rust-cache@v2 | |
| - name: Build Solana MCP Server | |
| run: cargo build --release | |
| - name: Install MCP Inspector and Zod | |
| run: | | |
| npm install -g @modelcontextprotocol/inspector@${{ env.INSPECTOR_VERSION }} | |
| npm install zod | |
| - name: Create Test Configuration | |
| run: | | |
| # Create a directory for test configuration if it doesn't exist | |
| mkdir -p test-config | |
| # Update the test config with the absolute path to the server binary | |
| cat > test-config/mcp-config.json << 'EOF' | |
| { | |
| "mcpServers": { | |
| "solana": { | |
| "command": "${{ github.workspace }}/target/release/solana-mcp-server", | |
| "args": ["stdio"], | |
| "env": { | |
| "SOLANA_RPC_URL": "https://api.devnet.solana.com", | |
| "SOLANA_COMMITMENT": "confirmed", | |
| "RUST_LOG": "info" | |
| } | |
| } | |
| } | |
| } | |
| EOF | |
| - name: Verify Binary and Configuration | |
| run: | | |
| echo "Verifying server binary..." | |
| ls -l ${{ github.workspace }}/target/release/solana-mcp-server | |
| echo "Verifying test configuration..." | |
| cat test-config/mcp-config.json | |
| # This single step replaces the multiple, fragmented test scripts | |
| - name: Run End-to-End Test with Schema Validation | |
| run: | | |
| cat > test-mcp-e2e.js << 'EOF' | |
| const { spawn } = require('child_process'); | |
| const { z } = require('zod'); | |
| // --- Zod Schemas for Validation --- | |
| const InitializeResultSchema = z.object({ | |
| protocolVersion: z.string(), | |
| serverInfo: z.object({ name: z.string(), version: z.string() }), | |
| capabilities: z.object({}).passthrough(), | |
| }); | |
| const ToolSchema = z.object({ | |
| name: z.string(), | |
| description: z.string().optional(), | |
| inputSchema: z.any(), | |
| }); | |
| const ToolsListResultSchema = z.object({ | |
| tools: z.array(ToolSchema), | |
| }); | |
| const ToolsCallResultSchema = z.any(); // The result can be anything, so we just check for presence | |
| // --- Test Logic --- | |
| async function main() { | |
| console.log('π Starting MCP server for E2E test...'); | |
| const serverProcess = spawn( | |
| '${{ github.workspace }}/target/release/solana-mcp-server', | |
| ['stdio'], | |
| { | |
| env: { | |
| ...process.env, | |
| SOLANA_RPC_URL: 'https://api.devnet.solana.com', | |
| RUST_LOG: 'info', | |
| }, | |
| } | |
| ); | |
| let responseBuffer = ''; | |
| const responsePromises = new Map(); | |
| serverProcess.stdout.on('data', (data) => { | |
| responseBuffer += data.toString(); | |
| const lines = responseBuffer.split('\n'); | |
| responseBuffer = lines.pop(); // Keep partial line in buffer | |
| for (const line of lines) { | |
| if (line.trim() === '') continue; | |
| try { | |
| const response = JSON.parse(line); | |
| console.log('π¨ Received from server:', JSON.stringify(response, null, 2)); | |
| if (response.id && responsePromises.has(response.id)) { | |
| responsePromises.get(response.id).resolve(response); | |
| responsePromises.delete(response.id); | |
| } | |
| } catch (error) { | |
| console.log('π Server Log:', line); | |
| } | |
| } | |
| }); | |
| serverProcess.stderr.on('data', (data) => console.error(`π΄ STDERR: ${data}`)); | |
| serverProcess.on('exit', (code) => { | |
| if (code !== 0 && code !== null) { | |
| console.error(`β Server exited prematurely with code: ${code}`); | |
| process.exit(1); | |
| } | |
| }); | |
| const sendRequest = (method, params, id) => { | |
| return new Promise((resolve, reject) => { | |
| const request = { jsonrpc: '2.0', id, method, params }; | |
| console.log(`π€ Sending to server (id: ${id}): ${method}`); | |
| serverProcess.stdin.write(JSON.stringify(request) + '\n'); | |
| responsePromises.set(id, { resolve, reject }); | |
| setTimeout(() => { | |
| if (responsePromises.has(id)) { | |
| reject(new Error(`Timeout waiting for response to request id ${id}`)); | |
| } | |
| }, 15000); // 15-second timeout | |
| }); | |
| }; | |
| try { | |
| // 1. Initialize | |
| const initResponse = await sendRequest('initialize', { | |
| protocolVersion: '2024-11-05', | |
| capabilities: {}, | |
| clientInfo: { name: 'e2e-test-client', version: '1.0.0' } | |
| }, 1); | |
| InitializeResultSchema.parse(initResponse.result); | |
| console.log('β Initialize response is valid.'); | |
| // 2. List Tools | |
| const listResponse = await sendRequest('tools/list', {}, 2); | |
| ToolsListResultSchema.parse(listResponse.result); | |
| console.log('β tools/list response is valid.'); | |
| // 3. Call a Tool (e.g., getHealth) | |
| const callResponse = await sendRequest('tools/call', { name: 'getHealth', arguments: {} }, 3); | |
| if (callResponse.error) throw new Error(`tools/call failed: ${callResponse.error.message}`); | |
| ToolsCallResultSchema.parse(callResponse.result); | |
| console.log('β tools/call response is valid.'); | |
| console.log('\nπ All tests passed!'); | |
| } catch (error) { | |
| console.error('\nβ Test failed:', error.message); | |
| if (error instanceof z.ZodError) { | |
| console.error('Schema validation details:', error.errors); | |
| } | |
| process.exit(1); | |
| } finally { | |
| serverProcess.kill(); | |
| } | |
| } | |
| main().catch(err => { | |
| console.error("Critical error during test execution:", err); | |
| process.exit(1); | |
| }); | |
| EOF | |
| node test-mcp-e2e.js | |
| - name: Test MCP Inspector CLI Compatibility | |
| run: | | |
| echo "π Testing MCP Inspector CLI compatibility..." | |
| # This test verifies that the inspector can connect, but we allow failure | |
| # as it can sometimes be flaky in headless CI environments. | |
| timeout 30s npx @modelcontextprotocol/inspector@${{ env.INSPECTOR_VERSION }} \ | |
| --config test-config/mcp-config.json \ | |
| --server solana \ | |
| --cli << 'INSPECTOREOF' || echo "β οΈ MCP Inspector CLI test failed or timed out (non-blocking)." | |
| list tools | |
| exit | |
| INSPECTOREOF | |
| - name: Generate Test Report | |
| if: always() # This step runs even if previous steps fail | |
| run: | | |
| echo "## MCP Inspector Compatibility Report" > report.md | |
| echo "" >> report.md | |
| echo "### Test Summary" >> report.md | |
| echo "- **Job Status**: ${{ job.status }}" >> report.md | |
| echo "- **Inspector Version**: ${{ env.INSPECTOR_VERSION }}" >> report.md | |
| echo "- **Timestamp**: $(date -u)" >> report.md | |
| echo "" >> report.md | |
| echo "### Server Configuration Used" >> report.md | |
| echo '```json' >> report.md | |
| cat test-config/mcp-config.json >> report.md | |
| echo '```' >> report.md | |
| - name: Upload Test Report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: mcp-inspector-report | |
| path: report.md | |
| retention-days: 7 |