Skip to main content
Glama

DollhouseMCP

by DollhouseMCP
qa-oauth-github-test.js12.6 kB
#!/usr/bin/env node /** * OAuth GitHub Repository Access Test * * This script tests the full OAuth flow including: * 1. Checking authentication status * 2. Initiating OAuth if needed * 3. Making authenticated GitHub API calls * 4. Accessing specific repository content */ import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; import chalk from 'chalk'; import open from 'open'; import readline from 'readline'; import { promisify } from 'util'; import { exec } from 'child_process'; import fs from 'fs/promises'; import { isTestMode } from './utils/github-auth.js'; const execAsync = promisify(exec); /** * WARNING: OAuth Testing Mode Support * This script supports both PAT (testing) and OAuth device flow (production) * - PAT Mode: Set TEST_GITHUB_TOKEN environment variable * - OAuth Mode: Leave TEST_GITHUB_TOKEN unset * See docs/development/OAUTH_TESTING_VS_PRODUCTION.md for critical differences */ class OAuthGitHubTest { constructor() { this.client = null; this.transport = null; this.githubToken = null; this.availableTools = []; } /** * Create readline interface for user input */ createReadlineInterface() { return readline.createInterface({ input: process.stdin, output: process.stdout }); } /** * Wait for user to press Enter */ async waitForEnter(message = 'Press Enter to continue...') { const rl = this.createReadlineInterface(); return new Promise((resolve) => { rl.question(message, () => { rl.close(); resolve(); }); }); } /** * Extract OAuth token from helper if it exists */ async getStoredToken() { try { // Check if token file exists using fs instead of shell const tokenPath = `${process.env.HOME}/.dollhouse/.github_token`; const token = await fs.readFile(tokenPath, 'utf-8'); const trimmedToken = token.trim(); if (trimmedToken && trimmedToken.startsWith('ghu_')) { return trimmedToken; } } catch (error) { // Token file doesn't exist or can't be read if (error.code !== 'ENOENT') { console.error(chalk.yellow('Warning: Error reading token file:', error.message)); } } return null; } /** * Complete OAuth flow */ async completeOAuthFlow() { console.log(chalk.yellow('\n🔐 Starting OAuth Authentication Flow...\n')); // Check current auth status const authResult = await this.callTool('check_github_auth'); console.log('Auth check result:', authResult.result?.[0]?.text?.substring(0, 200)); if (authResult.result?.[0]?.text?.includes('✅') && authResult.result?.[0]?.text?.includes('Authenticated')) { console.log(chalk.green('✅ Already authenticated!')); this.githubToken = await this.getStoredToken(); return true; } // Setup GitHub auth const setupResult = await this.callTool('setup_github_auth'); const setupText = setupResult.result?.[0]?.text || ''; // Extract device code const codeMatch = setupText.match(/Enter code:\s*\*\*([A-Z0-9]{4}-[A-Z0-9]{4})\*\*/) || setupText.match(/code:\s*\*\*([A-Z0-9]{4}-[A-Z0-9]{4})\*\*/i); if (!codeMatch) { console.error(chalk.red('Failed to get device code')); return false; } const deviceCode = codeMatch[1]; const verificationUrl = 'https://github.com/login/device'; console.log(chalk.cyan('\n' + '='.repeat(60))); console.log(chalk.bold.white('GitHub Device Authentication Required')); console.log(chalk.cyan('='.repeat(60))); console.log(chalk.yellow(`\n🔑 User Code: ${chalk.bold.white(deviceCode)}`)); console.log(chalk.yellow(`🌐 URL: ${chalk.bold.white(verificationUrl)}`)); console.log(chalk.cyan('\n' + '='.repeat(60) + '\n')); // Open browser console.log(chalk.blue('Opening browser...')); await open(verificationUrl); console.log(chalk.yellow('\nPlease enter the code in your browser and authorize the app.')); await this.waitForEnter('Press Enter after completing authentication...'); // Wait a moment for the helper to complete console.log(chalk.blue('Waiting for authentication to complete...')); await new Promise(resolve => setTimeout(resolve, 5000)); // Check if authentication succeeded const finalCheck = await this.callTool('check_github_auth'); if (finalCheck.result?.[0]?.text?.includes('✅')) { console.log(chalk.green('✅ Authentication successful!')); this.githubToken = await this.getStoredToken(); return true; } console.log(chalk.red('❌ Authentication failed')); return false; } /** * Make authenticated GitHub API request using fetch */ async githubFetch(url, options = {}) { const response = await fetch(url, { ...options, headers: { 'Authorization': `token ${this.githubToken}`, 'Accept': 'application/vnd.github.v3+json', ...options.headers } }); if (!response.ok) { const error = await response.json().catch(() => ({ message: response.statusText })); throw new Error(`GitHub API error: ${error.message || response.statusText}`); } return response.json(); } /** * Test GitHub API access with token */ async testGitHubAPI() { console.log(chalk.blue('\n📊 Testing Direct GitHub API Access...\n')); if (!this.githubToken) { this.githubToken = await this.getStoredToken(); if (!this.githubToken) { console.log(chalk.yellow('No token available for direct API testing')); return; } } try { // Test 1: Get authenticated user console.log(chalk.cyan('Getting authenticated user...')); const userData = await this.githubFetch('https://api.github.com/user'); console.log(chalk.green(`✅ Authenticated as: ${userData.login}`)); // Test 2: Access dollhouse-portfolio repository console.log(chalk.cyan('\nAccessing dollhouse-portfolio repository...')); const repoData = await this.githubFetch('https://api.github.com/repos/mickdarling/dollhouse-portfolio'); console.log(chalk.green(`✅ Repository: ${repoData.full_name}`)); console.log(` Description: ${repoData.description}`); console.log(` Private: ${repoData.private}`); // Test 3: List contents of templates folder console.log(chalk.cyan('\nListing templates folder contents...')); try { const contents = await this.githubFetch('https://api.github.com/repos/mickdarling/dollhouse-portfolio/contents/templates'); if (Array.isArray(contents)) { console.log(chalk.green(`✅ Found ${contents.length} items in templates folder:`)); contents.forEach(item => { console.log(` - ${item.name} (${item.type})`); }); // Test 4: Get a specific file content (if templates exist) const firstFile = contents.find(c => c.type === 'file'); if (firstFile) { console.log(chalk.cyan(`\nReading ${firstFile.name}...`)); const fileData = await this.githubFetch(firstFile.url); const content = Buffer.from(fileData.content, 'base64').toString('utf-8'); console.log(chalk.green(`✅ Successfully read ${firstFile.name}`)); console.log(chalk.gray(' First 200 chars:')); console.log(chalk.gray(` ${content.substring(0, 200)}...`)); } } } catch (error) { if (error.message.includes('404')) { console.log(chalk.yellow(' No templates folder found')); } else { throw error; } } } catch (error) { console.error(chalk.red('Error accessing GitHub API:'), error.message); } } /** * Test MCP tools that should use OAuth */ async testMCPGitHubTools() { console.log(chalk.blue('\n🔧 Testing MCP GitHub Integration Tools...\n')); // Look for portfolio/GitHub related tools const tools = this.availableTools.filter(tool => tool.includes('portfolio') || tool.includes('github') || tool.includes('submit') ); if (tools.length === 0) { console.log(chalk.yellow('No GitHub-related MCP tools found')); return; } console.log(chalk.cyan(`Found ${tools.length} potentially GitHub-related tools:`)); for (const toolName of tools) { console.log(`\n Testing: ${toolName}`); try { const result = await this.callTool(toolName); if (result.success) { console.log(chalk.green(` ✅ ${toolName} executed`)); const text = result.result?.[0]?.text || ''; if (text.includes('GitHub') || text.includes('authenticated')) { console.log(chalk.gray(` ${text.substring(0, 100)}...`)); } } else { console.log(chalk.yellow(` ⚠️ ${toolName}: ${result.error}`)); } } catch (error) { console.log(chalk.red(` ❌ ${toolName} failed: ${error.message}`)); } } } async connectToMCP() { console.log(chalk.blue('🔗 Connecting to MCP server...')); try { // Create transport and client this.transport = new StdioClientTransport({ command: 'node', args: ['dist/index.js'], cwd: process.cwd() }); this.client = new Client({ name: 'oauth-test-client', version: '1.0.0' }, { capabilities: {} }); await this.client.connect(this.transport); console.log(chalk.green('✅ Connected to MCP server')); // Discover tools const tools = await this.client.listTools(); this.availableTools = tools.tools.map(t => t.name); console.log(chalk.blue(`📋 Discovered ${this.availableTools.length} available tools`)); return true; } catch (error) { console.error(chalk.red('Failed to connect:'), error.message); throw error; } } async cleanup() { if (this.client) { await this.client.close(); } if (this.transport) { await this.transport.close(); } } async callTool(toolName, args = {}) { try { const result = await this.client.callTool({ name: toolName, arguments: args }); return { success: true, result: result.content }; } catch (error) { return { success: false, error: error.message }; } } async runTests() { console.log(chalk.bold.cyan('\n' + '='.repeat(60))); console.log(chalk.bold.white(' OAuth GitHub Repository Access Test')); console.log(chalk.bold.cyan('='.repeat(60) + '\n')); // Step 1: Complete OAuth flow if needed const authSuccess = await this.completeOAuthFlow(); if (!authSuccess) { console.log(chalk.red('\n❌ OAuth authentication failed. Cannot proceed with tests.')); return; } // Step 2: Test direct GitHub API access await this.testGitHubAPI(); // Step 3: Test MCP tools that should use OAuth await this.testMCPGitHubTools(); // Summary console.log(chalk.bold.green('\n' + '='.repeat(60))); console.log(chalk.bold.white(' Test Summary')); console.log(chalk.bold.green('='.repeat(60))); console.log(chalk.cyan('\nResults:')); if (isTestMode()) { console.log(chalk.green(' ✅ PAT authentication working (TEST MODE)')); console.log(chalk.yellow(' ⚠️ OAuth device flow NOT tested (requires manual testing)')); } else { console.log(chalk.green(' ✅ OAuth authentication working')); } console.log(chalk.green(' ✅ Direct GitHub API access confirmed')); console.log(chalk.green(' ✅ Token storage and retrieval working')); if (this.githubToken) { console.log(chalk.yellow('\n ⚠️ Note: MCP tools may need updates to use OAuth token')); console.log(chalk.gray(' Currently, MCP tools and direct API calls are separate')); } } } // Run the test if (import.meta.url === `file://${process.argv[1]}`) { const test = new OAuthGitHubTest(); (async () => { try { await test.connectToMCP(); await test.runTests(); process.exit(0); } catch (error) { console.error(chalk.red('Fatal error:'), error); process.exit(1); } finally { await test.cleanup(); } })(); } export default OAuthGitHubTest;

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/DollhouseMCP/DollhouseMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server