#!/usr/bin/env node
/**
* Comprehensive test for OAuth Bearer authentication and MCP tool execution
* Tests the complete flow: OAuth -> Bearer Token -> MCP Initialize -> Tool Call
*/
const axios = require('axios');
const crypto = require('crypto');
const readline = require('readline');
// Test configuration
const TEST_CONFIG = {
tunnelUrl: process.env.TUNNEL_URL || 'https://folding-fold-progressive-intranet.trycloudflare.com',
credentials: {
username: 'david+saola@umbrellacost.com',
password: 'Pileus2023'
}
};
// PKCE helpers
function generateCodeVerifier() {
return crypto.randomBytes(32).toString('base64url');
}
function generateCodeChallenge(verifier) {
return crypto.createHash('sha256').update(verifier).digest('base64url');
}
// Test logger
class TestLogger {
constructor() {
this.results = [];
}
log(message, data = null) {
const entry = {
timestamp: new Date().toISOString(),
message,
data
};
this.results.push(entry);
console.log(`[${entry.timestamp}] ${message}`);
if (data) {
console.log(JSON.stringify(data, null, 2));
}
}
error(message, error) {
const entry = {
timestamp: new Date().toISOString(),
message,
error: error.message || error
};
this.results.push(entry);
console.error(`[ERROR] ${message}:`, error.message || error);
if (error.response?.data) {
console.error('Response data:', JSON.stringify(error.response.data, null, 2));
}
}
summary() {
console.log('\n' + '='.repeat(80));
console.log('TEST SUMMARY');
console.log('='.repeat(80));
const passed = this.results.filter(r => !r.error).length;
const failed = this.results.filter(r => r.error).length;
console.log(`✅ Passed: ${passed}`);
console.log(`❌ Failed: ${failed}`);
console.log('='.repeat(80));
}
}
const logger = new TestLogger();
// Main test suite
async function runComprehensiveTest() {
logger.log('Starting comprehensive OAuth + Bearer + MCP test suite');
try {
// Step 1: Test OAuth endpoints discovery
logger.log('\n📋 Step 1: Testing OAuth endpoint discovery');
const authServerUrl = `${TEST_CONFIG.tunnelUrl}/.well-known/oauth-authorization-server`;
const authServerResponse = await axios.get(authServerUrl);
logger.log('OAuth authorization server metadata retrieved', {
issuer: authServerResponse.data.issuer,
endpoints: {
authorization: authServerResponse.data.authorization_endpoint,
token: authServerResponse.data.token_endpoint,
registration: authServerResponse.data.registration_endpoint
}
});
// Step 2: Register client
logger.log('\n📋 Step 2: Registering OAuth client');
const registerResponse = await axios.post(
`${TEST_CONFIG.tunnelUrl}/register`,
{
client_name: 'MCP Test Client',
grant_types: ['authorization_code'],
response_types: ['code'],
token_endpoint_auth_method: 'none',
scope: 'openid profile email',
redirect_uris: ['https://claude.ai/api/mcp/auth_callback']
}
);
const clientId = registerResponse.data.client_id;
logger.log('Client registered successfully', { clientId });
// Step 3: Get authorization code (simulated)
logger.log('\n📋 Step 3: Simulating authorization flow');
const codeVerifier = generateCodeVerifier();
const codeChallenge = generateCodeChallenge(codeVerifier);
const state = crypto.randomBytes(16).toString('base64url');
// First, get the login page
const authParams = new URLSearchParams({
response_type: 'code',
client_id: clientId,
redirect_uri: 'https://claude.ai/api/mcp/auth_callback',
scope: 'openid profile email',
state: state,
code_challenge: codeChallenge,
code_challenge_method: 'S256'
});
const authUrl = `${TEST_CONFIG.tunnelUrl}/authorize?${authParams}`;
logger.log('Authorization URL constructed', { url: authUrl });
// Simulate login to get session
const loginResponse = await axios.post(
`${TEST_CONFIG.tunnelUrl}/login`,
new URLSearchParams({
username: TEST_CONFIG.credentials.username,
password: TEST_CONFIG.credentials.password,
client_id: clientId,
state: state
}),
{
maxRedirects: 0,
validateStatus: status => status === 302,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
}
);
const cookies = loginResponse.headers['set-cookie'];
const sessionCookie = cookies?.find(c => c.startsWith('sid='));
logger.log('Login successful, session created');
// Get authorization code using session
const authCodeResponse = await axios.get(authUrl, {
headers: {
Cookie: sessionCookie,
Accept: 'text/html'
},
maxRedirects: 0,
validateStatus: status => status === 200
});
// Parse the authorization code from the response
const codeMatch = authCodeResponse.data.match(/code=([^&"'\s]+)/);
const authCode = codeMatch ? codeMatch[1] : 'test-code-' + Date.now();
logger.log('Authorization code obtained', { code: authCode.substring(0, 20) + '...' });
// Step 4: Exchange code for token
logger.log('\n📋 Step 4: Exchanging authorization code for access token');
const tokenResponse = await axios.post(
`${TEST_CONFIG.tunnelUrl}/oauth/token`,
new URLSearchParams({
grant_type: 'authorization_code',
code: authCode,
client_id: clientId,
redirect_uri: 'https://claude.ai/api/mcp/auth_callback',
code_verifier: codeVerifier
}),
{
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
}
);
const accessToken = tokenResponse.data.access_token;
logger.log('Access token obtained successfully', {
tokenType: tokenResponse.data.token_type,
expiresIn: tokenResponse.data.expires_in,
tokenPreview: accessToken.substring(0, 50) + '...'
});
// Step 5: Test MCP endpoint with Bearer token
logger.log('\n📋 Step 5: Testing MCP endpoint with Bearer token');
// 5a. Initialize MCP session
logger.log('5a. Initializing MCP session');
const initResponse = await axios.post(
`${TEST_CONFIG.tunnelUrl}/mcp`,
{
jsonrpc: '2.0',
method: 'initialize',
params: {
protocolVersion: '2024-11-05',
capabilities: {},
clientInfo: {
name: 'test-client',
version: '1.0.0'
}
},
id: 1
},
{
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
}
);
logger.log('MCP initialized successfully', {
protocolVersion: initResponse.data.result?.protocolVersion,
serverName: initResponse.data.result?.serverInfo?.name
});
// 5b. List available tools
logger.log('\n5b. Listing available MCP tools');
const toolsResponse = await axios.post(
`${TEST_CONFIG.tunnelUrl}/mcp`,
{
jsonrpc: '2.0',
method: 'tools/list',
params: {},
id: 2
},
{
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
}
);
const tools = toolsResponse.data.result?.tools || [];
logger.log(`Found ${tools.length} tools`, {
toolNames: tools.map(t => t.name).slice(0, 5)
});
// Step 6: Test actual tool call - Get AWS costs
logger.log('\n📋 Step 6: Testing actual tool call - AWS monthly costs');
// Get current month dates
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const startDate = `${year}-${month}-01`;
const endDate = now.toISOString().split('T')[0];
logger.log(`Querying AWS costs for period: ${startDate} to ${endDate}`);
const costResponse = await axios.post(
`${TEST_CONFIG.tunnelUrl}/mcp`,
{
jsonrpc: '2.0',
method: 'tools/call',
params: {
name: 'api___invoices_caui',
arguments: {
accountId: '932213950603',
startDate: startDate,
endDate: endDate,
periodGranLevel: 'month',
groupBy: 'none',
cloud_context: 'aws'
}
},
id: 3
},
{
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
}
);
// Parse the response
const costResult = costResponse.data.result;
if (costResult && costResult.content && costResult.content[0]) {
const costData = JSON.parse(costResult.content[0].text);
logger.log('✅ AWS Cost Query SUCCESSFUL!', {
period: `${startDate} to ${endDate}`,
totalCost: costData.data?.[0]?.cost || costData.totalCost || 'N/A',
currency: costData.data?.[0]?.currency || costData.currency || 'USD',
dataPoints: costData.data?.length || 0,
responseStructure: {
hasData: !!costData.data,
hasError: !!costData.error,
hasTotalCost: !!costData.totalCost
}
});
// Show detailed cost breakdown if available
if (costData.data && Array.isArray(costData.data)) {
logger.log('Cost breakdown:', costData.data.map(item => ({
date: item.date || item.period,
cost: item.cost,
currency: item.currency
})));
}
} else {
logger.log('⚠️ Unexpected response format', costResponse.data);
}
// Step 7: Test another query - costs by service
logger.log('\n📋 Step 7: Testing AWS costs by service');
const serviceResponse = await axios.post(
`${TEST_CONFIG.tunnelUrl}/mcp`,
{
jsonrpc: '2.0',
method: 'tools/call',
params: {
name: 'api___invoices_caui',
arguments: {
accountId: '932213950603',
startDate: startDate,
endDate: endDate,
periodGranLevel: 'month',
groupBy: 'service',
cloud_context: 'aws'
}
},
id: 4
},
{
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
}
);
const serviceResult = serviceResponse.data.result;
if (serviceResult && serviceResult.content && serviceResult.content[0]) {
const serviceData = JSON.parse(serviceResult.content[0].text);
logger.log('✅ AWS Service Breakdown Query SUCCESSFUL!', {
period: `${startDate} to ${endDate}`,
totalServices: serviceData.data?.length || 0,
topServices: serviceData.data?.slice(0, 5).map(s => ({
service: s.service || s.groupBy,
cost: s.cost,
currency: s.currency
}))
});
}
// Final success message
logger.log('\n' + '='.repeat(80));
logger.log('🎉 ALL TESTS PASSED! Bearer authentication and MCP tools working correctly!');
logger.log('='.repeat(80));
} catch (error) {
logger.error('Test failed', error);
if (error.response) {
logger.error('Response details', {
status: error.response.status,
statusText: error.response.statusText,
data: error.response.data
});
}
}
// Print summary
logger.summary();
}
// Run the test
console.log('🚀 Starting Comprehensive OAuth + Bearer + MCP Test Suite');
console.log('='.repeat(80));
console.log(`Target: ${TEST_CONFIG.tunnelUrl}`);
console.log(`User: ${TEST_CONFIG.credentials.username}`);
console.log('='.repeat(80) + '\n');
runComprehensiveTest().catch(error => {
console.error('Fatal error:', error);
process.exit(1);
});