99
1010env :
1111 CARGO_TERM_COLOR : always
12- RUST_BACKTRACE : 1
12+ RUST_BACKTRACE : full
1313 NODE_VERSION : ' 18'
14- INSPECTOR_VERSION : ' 0.16.2'
14+ # Note: The inspector package name is different in your example. Adjust as needed.
15+ INSPECTOR_PACKAGE :
' @open-svm/[email protected] ' 16+ MCP_HOST : ' 127.0.0.1'
17+ MCP_PORT : 31902
1518
1619jobs :
1720 mcp-inspector-test :
18- name : Test MCP Inspector Compatibility
21+ name : Test MCP Server with Inspector CLI
1922 runs-on : ubuntu-latest
2023 timeout-minutes : 20
2124
@@ -33,206 +36,107 @@ jobs:
3336 with :
3437 node-version : ${{ env.NODE_VERSION }}
3538
36- # Use the recommended action for caching Rust dependencies
3739 - name : Cache Rust dependencies
3840 uses : Swatinem/rust-cache@v2
3941
40- - name : Build Solana MCP Server
42+ - name : Build solana-mcp-server
4143 run : cargo build --release
4244
43- - name : Install MCP Inspector and Zod
45+ - name : Install MCP Inspector CLI and Zod
4446 run : |
45- npm install -g @modelcontextprotocol/inspector@ ${{ env.INSPECTOR_VERSION }}
47+ npm install -g ${{ env.INSPECTOR_PACKAGE }}
4648 npm install zod
4749
48- - name : Create Test Configuration
50+ - name : Create MCP Server Config
4951 run : |
50- # Create a directory for test configuration if it doesn't exist
5152 mkdir -p test-config
52-
53- # Update the test config with the absolute path to the server binary
54- cat > test-config/mcp-config.json << 'EOF'
53+ cat <<EOF > test-config/mcp-config.json
5554 {
56- "mcpServers": {
57- "solana": {
58- "command": "${{ github.workspace }}/target/release/solana-mcp-server",
59- "args": ["stdio"],
60- "env": {
61- "SOLANA_RPC_URL": "https://api.devnet.solana.com",
62- "SOLANA_COMMITMENT": "confirmed",
63- "RUST_LOG": "info"
64- }
65- }
66- }
55+ "network": "devnet",
56+ "rpc_url": "https://api.devnet.solana.com",
57+ "mcp_port": ${{ env.MCP_PORT }},
58+ "mcp_host": "${{ env.MCP_HOST }}",
59+ "mcp_token": ""
6760 }
6861 EOF
6962
70- - name : Verify Binary and Configuration
63+ - name : Start Server and Wait for it to be Ready
7164 run : |
72- echo "Verifying server binary..."
73- ls -l ${{ github.workspace }}/target/release/solana-mcp-server
74- echo "Verifying test configuration..."
75- cat test-config/mcp-config.json
65+ echo "Starting server in the background..."
66+ ./target/release/solana-mcp-server --config test-config/mcp-config.json > server.log 2>&1 &
67+
68+ echo "Waiting for server to become available..."
69+ timeout=120
70+ for i in $(seq 1 $timeout); do
71+ if mcp-inspector ping --host ${{ env.MCP_HOST }} --port ${{ env.MCP_PORT }}; then
72+ echo "✅ Server is up and running!"
73+ exit 0
74+ fi
75+ echo "Still waiting for server... (${i}s)"
76+ sleep 1
77+ done
78+
79+ echo "❌ Server did not start within $timeout seconds."
80+ echo "--- Server Log ---"
81+ cat server.log
82+ exit 1
7683
77- # This single step replaces the multiple, fragmented test scripts
78- - name : Run End-to-End Test with Schema Validation
84+ - name : Run Inspector CLI Tests and Validate Schema
7985 run : |
80- cat > test-mcp-e2e.js << 'EOF'
81- const { spawn } = require('child_process');
82- const { z } = require('zod');
86+ echo "--- Testing 'initialize' method ---"
87+ mcp-inspector call --host ${{ env.MCP_HOST }} --port ${{ env.MCP_PORT }} --method "initialize" --params '{}' --json > initialize_response.json
88+ cat initialize_response.json
89+
90+ echo "--- Testing 'tools/list' method ---"
91+ mcp-inspector call --host ${{ env.MCP_HOST }} --port ${{ env.MCP_PORT }} --method "tools/list" --params '{}' --json > tools_list_response.json
92+ cat tools_list_response.json
8393
84- // --- Zod Schemas for Validation ---
85- const InitializeResultSchema = z.object({
86- protocolVersion: z.string(),
87- serverInfo: z.object({ name: z.string(), version: z.string() }),
88- capabilities: z.object({}).passthrough(),
89- });
90-
91- const ToolSchema = z.object({
92- name: z.string(),
93- description: z.string().optional(),
94- inputSchema: z.any(),
95- });
96-
97- const ToolsListResultSchema = z.object({
98- tools: z.array(ToolSchema),
99- });
100-
101- const ToolsCallResultSchema = z.any(); // The result can be anything, so we just check for presence
102-
103- // --- Test Logic ---
104- async function main() {
105- console.log('🚀 Starting MCP server for E2E test...');
106- const serverProcess = spawn(
107- '${{ github.workspace }}/target/release/solana-mcp-server',
108- ['stdio'],
109- {
110- env: {
111- ...process.env,
112- SOLANA_RPC_URL: 'https://api.devnet.solana.com',
113- RUST_LOG: 'info',
114- },
115- }
116- );
117-
118- let responseBuffer = '';
119- const responsePromises = new Map();
120-
121- serverProcess.stdout.on('data', (data) => {
122- responseBuffer += data.toString();
123- const lines = responseBuffer.split('\n');
124- responseBuffer = lines.pop(); // Keep partial line in buffer
125-
126- for (const line of lines) {
127- if (line.trim() === '') continue;
128- try {
129- const response = JSON.parse(line);
130- console.log('📨 Received from server:', JSON.stringify(response, null, 2));
131- if (response.id && responsePromises.has(response.id)) {
132- responsePromises.get(response.id).resolve(response);
133- responsePromises.delete(response.id);
134- }
135- } catch (error) {
136- console.log('📝 Server Log:', line);
137- }
138- }
94+ echo "--- Validating response schemas ---"
95+ node <<EOF
96+ const { z } = require('zod');
97+ const fs = require('fs');
98+
99+ try {
100+ const initResp = JSON.parse(fs.readFileSync('initialize_response.json', 'utf8'));
101+ const toolsResp = JSON.parse(fs.readFileSync('tools_list_response.json', 'utf8'));
102+
103+ // Define schemas based on the MCP spec
104+ const initializeResultSchema = z.object({
105+ protocolVersion: z.string(),
106+ serverInfo: z.object({ name: z.string(), version: z.string() }),
107+ }).passthrough();
108+
109+ const toolSchema = z.object({
110+ name: z.string(),
111+ description: z.string().optional(),
112+ inputSchema: z.any()
139113 });
140114
141- serverProcess.stderr.on('data', (data) => console.error(`🔴 STDERR: ${data}`));
142- serverProcess.on('exit', (code) => {
143- if (code !== 0 && code !== null) {
144- console.error(`❌ Server exited prematurely with code: ${code}`);
145- process.exit(1);
146- }
115+ const toolsListResultSchema = z.object({
116+ tools: z.array(toolSchema),
147117 });
148118
149- const sendRequest = (method, params, id) => {
150- return new Promise((resolve, reject) => {
151- const request = { jsonrpc: '2.0', id, method, params };
152- console.log(`📤 Sending to server (id: ${id}): ${method}`);
153- serverProcess.stdin.write(JSON.stringify(request) + '\n');
154- responsePromises.set(id, { resolve, reject });
155- setTimeout(() => {
156- if (responsePromises.has(id)) {
157- reject(new Error(`Timeout waiting for response to request id ${id}`));
158- }
159- }, 15000); // 15-second timeout
160- });
161- };
162-
163- try {
164- // 1. Initialize
165- const initResponse = await sendRequest('initialize', {
166- protocolVersion: '2024-11-05',
167- capabilities: {},
168- clientInfo: { name: 'e2e-test-client', version: '1.0.0' }
169- }, 1);
170- InitializeResultSchema.parse(initResponse.result);
171- console.log('✅ Initialize response is valid.');
172-
173- // 2. List Tools
174- const listResponse = await sendRequest('tools/list', {}, 2);
175- ToolsListResultSchema.parse(listResponse.result);
176- console.log('✅ tools/list response is valid.');
177-
178- // 3. Call a Tool (e.g., getHealth)
179- const callResponse = await sendRequest('tools/call', { name: 'getHealth', arguments: {} }, 3);
180- if (callResponse.error) throw new Error(`tools/call failed: ${callResponse.error.message}`);
181- ToolsCallResultSchema.parse(callResponse.result);
182- console.log('✅ tools/call response is valid.');
183-
184- console.log('\n🎉 All tests passed!');
185- } catch (error) {
186- console.error('\n❌ Test failed:', error.message);
187- if (error instanceof z.ZodError) {
188- console.error('Schema validation details:', error.errors);
189- }
190- process.exit(1);
191- } finally {
192- serverProcess.kill();
193- }
194- }
119+ // Validate 'initialize'
120+ initializeResultSchema.parse(initResp.result);
121+ console.log("✅ 'initialize' response schema is valid.");
195122
196- main().catch(err => {
197- console.error("Critical error during test execution:", err);
198- process.exit(1);
199- });
200- EOF
123+ // Validate 'tools/list'
124+ toolsListResultSchema.parse(toolsResp.result);
125+ console.log("✅ 'tools/list' response schema is valid.");
201126
202- node test-mcp-e2e.js
127+ } catch (error) {
128+ console.error("❌ Schema validation failed:", error.message);
129+ process.exit(1);
130+ }
131+ EOF
203132
204- - name : Test MCP Inspector CLI Compatibility
205- run : |
206- echo "🔍 Testing MCP Inspector CLI compatibility..."
207- # This test verifies that the inspector can connect, but we allow failure
208- # as it can sometimes be flaky in headless CI environments.
209- timeout 30s npx @modelcontextprotocol/inspector@${{ env.INSPECTOR_VERSION }} \
210- --config test-config/mcp-config.json \
211- --server solana \
212- --cli << 'INSPECTOREOF' || echo "⚠️ MCP Inspector CLI test failed or timed out (non-blocking)."
213- list tools
214- exit
215- INSPECTOREOF
216-
217- - name : Generate Test Report
218- if : always() # This step runs even if previous steps fail
219- run : |
220- echo "## MCP Inspector Compatibility Report" > report.md
221- echo "" >> report.md
222- echo "### Test Summary" >> report.md
223- echo "- **Job Status**: ${{ job.status }}" >> report.md
224- echo "- **Inspector Version**: ${{ env.INSPECTOR_VERSION }}" >> report.md
225- echo "- **Timestamp**: $(date -u)" >> report.md
226- echo "" >> report.md
227- echo "### Server Configuration Used" >> report.md
228- echo '```json' >> report.md
229- cat test-config/mcp-config.json >> report.md
230- echo '```' >> report.md
231-
232- - name : Upload Test Report
133+ - name : Upload Test Artifacts
233134 if : always()
234135 uses : actions/upload-artifact@v4
235136 with :
236- name : mcp-inspector-report
237- path : report.md
137+ name : mcp-test-artifacts
138+ path : |
139+ server.log
140+ initialize_response.json
141+ tools_list_response.json
238142 retention-days : 7
0 commit comments