Skip to main content
Glama

AFFiNE MCP Server

by DAWNCR0W
test-comprehensive.mjs25.3 kB
#!/usr/bin/env node import { spawn } from 'child_process'; import { config } from 'dotenv'; import fs from 'fs'; config(); const MCP_SERVER_PATH = './dist/index.js'; // Tool categories for 50 tools const TOOL_CATEGORIES = { workspace: [ 'affine_list_workspaces', 'affine_get_workspace', 'affine_create_workspace', 'affine_update_workspace', 'affine_delete_workspace', 'affine_leave_workspace', 'affine_invite_members' ], document: [ 'affine_list_docs', 'affine_get_doc', 'affine_search_docs', 'affine_recent_docs', 'affine_publish_doc', 'affine_revoke_doc', 'affine_create_doc', 'affine_update_doc', 'affine_delete_doc', 'affine_duplicate_doc', 'affine_restore_doc', 'affine_share_doc' ], collaboration: [ 'affine_list_comments', 'affine_create_comment', 'affine_update_comment', 'affine_delete_comment', 'affine_resolve_comment' ], history: [ 'affine_list_histories', 'affine_recover_doc' ], user: [ 'affine_current_user', 'affine_sign_in', 'affine_update_profile', 'affine_update_settings', 'affine_send_verify_email', 'affine_change_password', 'affine_send_password_reset', 'affine_delete_account' ], accessToken: [ 'affine_list_access_tokens', 'affine_generate_access_token', 'affine_revoke_access_token' ], blob: [ 'affine_upload_blob', 'affine_delete_blob', 'affine_cleanup_blobs' ], notification: [ 'affine_list_notifications', 'affine_read_notification', 'affine_read_all_notifications' ], advanced: [ 'affine_apply_doc_updates' ], realtime: [ 'affine_realtime_create_doc', 'affine_realtime_update_doc', 'affine_realtime_connect' ], experimental: [ 'affine_experimental_create_workspace', 'affine_experimental_init_doc', 'affine_experimental_clone_doc' ] }; class ComprehensiveTestRunner { constructor() { this.server = null; this.testResults = {}; this.workspaceId = null; this.docId = null; this.commentId = null; this.tokenId = null; this.blobKey = null; this.totalTools = 0; } async startServer() { return new Promise((resolve, reject) => { this.server = spawn('node', [MCP_SERVER_PATH], { env: { ...process.env }, stdio: ['pipe', 'pipe', 'pipe'] }); this.server.on('error', reject); setTimeout(resolve, 2000); }); } async sendRequest(method, params = {}) { return new Promise((resolve) => { const request = { jsonrpc: '2.0', method: 'tools/call', params: { name: method, arguments: params }, id: Date.now() }; this.server.stdin.write(JSON.stringify(request) + '\n'); const handler = (data) => { const lines = data.toString().split('\n').filter(line => line.trim()); for (const line of lines) { try { const response = JSON.parse(line); if (response.id === request.id) { this.server.stdout.removeListener('data', handler); resolve(response); } } catch (e) { // Continue } } }; this.server.stdout.on('data', handler); setTimeout(() => { this.server.stdout.removeListener('data', handler); resolve({ error: { message: 'Timeout' } }); }, 10000); }); } async testTool(name, params) { const startTime = Date.now(); try { const result = await this.sendRequest(name, params); const duration = Date.now() - startTime; if (result.error) { return { status: 'failed', error: result.error.message || JSON.stringify(result.error), duration }; } else { return { status: 'success', duration, result: result.result }; } } catch (error) { return { status: 'error', error: error.message }; } } async testWorkspaceTools() { console.log('\n📁 WORKSPACE MANAGEMENT (7 tools)'); const results = {}; // List workspaces results['affine_list_workspaces'] = await this.testTool('affine_list_workspaces', {}); console.log(` ✅ affine_list_workspaces`); // Create workspace const createResult = await this.testTool('affine_create_workspace', { name: 'Test Workspace' }); results['affine_create_workspace'] = createResult; if (createResult.status === 'success' && createResult.result?.content?.[0]?.text) { const text = createResult.result.content[0].text; const match = text.match(/"id":\s*"([^"]+)"/); if (match) this.workspaceId = match[1]; } console.log(` ✅ affine_create_workspace`); if (this.workspaceId) { // Get workspace results['affine_get_workspace'] = await this.testTool('affine_get_workspace', { id: this.workspaceId }); console.log(` ✅ affine_get_workspace`); // Update workspace results['affine_update_workspace'] = await this.testTool('affine_update_workspace', { id: this.workspaceId, enableAi: true }); console.log(` ✅ affine_update_workspace`); // Invite members results['affine_invite_members'] = await this.testTool('affine_invite_members', { workspaceId: this.workspaceId, emails: ['test@example.com'], sendInviteMail: false }); console.log(` ✅ affine_invite_members`); // Leave workspace results['affine_leave_workspace'] = await this.testTool('affine_leave_workspace', { workspaceId: this.workspaceId }); console.log(` ✅ affine_leave_workspace`); // Delete workspace results['affine_delete_workspace'] = await this.testTool('affine_delete_workspace', { id: this.workspaceId }); console.log(` ✅ affine_delete_workspace`); } else { results['affine_get_workspace'] = { status: 'skipped', reason: 'No workspace ID' }; results['affine_update_workspace'] = { status: 'skipped', reason: 'No workspace ID' }; results['affine_invite_members'] = { status: 'skipped', reason: 'No workspace ID' }; results['affine_leave_workspace'] = { status: 'skipped', reason: 'No workspace ID' }; results['affine_delete_workspace'] = { status: 'skipped', reason: 'No workspace ID' }; } return results; } async testDocumentTools() { console.log('\n📄 DOCUMENT OPERATIONS (12 tools)'); const results = {}; // List docs results['affine_list_docs'] = await this.testTool('affine_list_docs', { first: 5 }); console.log(` ✅ affine_list_docs`); // Search docs results['affine_search_docs'] = await this.testTool('affine_search_docs', { keyword: 'test' }); console.log(` ✅ affine_search_docs`); // Recent docs results['affine_recent_docs'] = await this.testTool('affine_recent_docs', { first: 5 }); console.log(` ✅ affine_recent_docs`); // Create doc const createResult = await this.testTool('affine_create_doc', { title: 'Test Document' }); results['affine_create_doc'] = createResult; if (createResult.status === 'success' && createResult.result?.content?.[0]?.text) { const text = createResult.result.content[0].text; const match = text.match(/"id":\s*"([^"]+)"/); if (match) this.docId = match[1]; } console.log(` ✅ affine_create_doc`); if (this.docId) { // Get doc results['affine_get_doc'] = await this.testTool('affine_get_doc', { docId: this.docId }); console.log(` ✅ affine_get_doc`); // Update doc results['affine_update_doc'] = await this.testTool('affine_update_doc', { docId: this.docId, content: 'Updated content' }); console.log(` ✅ affine_update_doc`); // Publish doc results['affine_publish_doc'] = await this.testTool('affine_publish_doc', { docId: this.docId }); console.log(` ✅ affine_publish_doc`); // Revoke doc results['affine_revoke_doc'] = await this.testTool('affine_revoke_doc', { docId: this.docId }); console.log(` ✅ affine_revoke_doc`); // Duplicate doc results['affine_duplicate_doc'] = await this.testTool('affine_duplicate_doc', { docId: this.docId, newTitle: 'Duplicated Doc' }); console.log(` ✅ affine_duplicate_doc`); // Share doc results['affine_share_doc'] = await this.testTool('affine_share_doc', { docId: this.docId, users: ['test@example.com'] }); console.log(` ✅ affine_share_doc`); // Delete doc results['affine_delete_doc'] = await this.testTool('affine_delete_doc', { docId: this.docId }); console.log(` ✅ affine_delete_doc`); // Restore doc results['affine_restore_doc'] = await this.testTool('affine_restore_doc', { docId: this.docId }); console.log(` ✅ affine_restore_doc`); } else { results['affine_get_doc'] = { status: 'skipped', reason: 'No doc ID' }; results['affine_update_doc'] = { status: 'skipped', reason: 'No doc ID' }; results['affine_publish_doc'] = { status: 'skipped', reason: 'No doc ID' }; results['affine_revoke_doc'] = { status: 'skipped', reason: 'No doc ID' }; results['affine_duplicate_doc'] = { status: 'skipped', reason: 'No doc ID' }; results['affine_share_doc'] = { status: 'skipped', reason: 'No doc ID' }; results['affine_delete_doc'] = { status: 'skipped', reason: 'No doc ID' }; results['affine_restore_doc'] = { status: 'skipped', reason: 'No doc ID' }; } return results; } async testCollaborationTools() { console.log('\n💬 COLLABORATION (5 tools)'); const results = {}; if (this.docId) { // List comments results['affine_list_comments'] = await this.testTool('affine_list_comments', { docId: this.docId }); console.log(` ✅ affine_list_comments`); // Create comment const createResult = await this.testTool('affine_create_comment', { docId: this.docId, content: 'Test comment' }); results['affine_create_comment'] = createResult; if (createResult.status === 'success' && createResult.result?.content?.[0]?.text) { const text = createResult.result.content[0].text; const match = text.match(/"id":\s*"([^"]+)"/); if (match) this.commentId = match[1]; } console.log(` ✅ affine_create_comment`); if (this.commentId) { // Update comment results['affine_update_comment'] = await this.testTool('affine_update_comment', { id: this.commentId, content: 'Updated comment' }); console.log(` ✅ affine_update_comment`); // Resolve comment results['affine_resolve_comment'] = await this.testTool('affine_resolve_comment', { id: this.commentId, resolved: true }); console.log(` ✅ affine_resolve_comment`); // Delete comment results['affine_delete_comment'] = await this.testTool('affine_delete_comment', { id: this.commentId }); console.log(` ✅ affine_delete_comment`); } else { results['affine_update_comment'] = { status: 'skipped', reason: 'No comment ID' }; results['affine_resolve_comment'] = { status: 'skipped', reason: 'No comment ID' }; results['affine_delete_comment'] = { status: 'skipped', reason: 'No comment ID' }; } } else { results['affine_list_comments'] = { status: 'skipped', reason: 'No doc ID' }; results['affine_create_comment'] = { status: 'skipped', reason: 'No doc ID' }; results['affine_update_comment'] = { status: 'skipped', reason: 'No doc ID' }; results['affine_resolve_comment'] = { status: 'skipped', reason: 'No doc ID' }; results['affine_delete_comment'] = { status: 'skipped', reason: 'No doc ID' }; } return results; } async testHistoryTools() { console.log('\n📚 VERSION CONTROL (2 tools)'); const results = {}; if (this.docId) { results['affine_list_histories'] = await this.testTool('affine_list_histories', { guid: this.docId }); console.log(` ✅ affine_list_histories`); results['affine_recover_doc'] = await this.testTool('affine_recover_doc', { guid: this.docId, timestamp: new Date().toISOString() }); console.log(` ✅ affine_recover_doc`); } else { results['affine_list_histories'] = { status: 'skipped', reason: 'No doc ID' }; results['affine_recover_doc'] = { status: 'skipped', reason: 'No doc ID' }; } return results; } async testUserTools() { console.log('\n👤 USER & AUTHENTICATION (8 tools)'); const results = {}; results['affine_current_user'] = await this.testTool('affine_current_user', {}); console.log(` ✅ affine_current_user`); results['affine_sign_in'] = await this.testTool('affine_sign_in', { email: process.env.AFFINE_EMAIL, password: process.env.AFFINE_PASSWORD }); console.log(` ✅ affine_sign_in`); results['affine_update_profile'] = await this.testTool('affine_update_profile', { name: 'Test User' }); console.log(` ✅ affine_update_profile`); results['affine_update_settings'] = await this.testTool('affine_update_settings', { settings: { theme: 'dark' } }); console.log(` ✅ affine_update_settings`); results['affine_send_verify_email'] = await this.testTool('affine_send_verify_email', {}); console.log(` ✅ affine_send_verify_email`); results['affine_send_password_reset'] = await this.testTool('affine_send_password_reset', {}); console.log(` ✅ affine_send_password_reset`); // Skip destructive operations results['affine_change_password'] = { status: 'skipped', reason: 'Requires token' }; results['affine_delete_account'] = { status: 'skipped', reason: 'Destructive operation' }; return results; } async testAccessTokenTools() { console.log('\n🔑 ACCESS TOKENS (3 tools)'); const results = {}; results['affine_list_access_tokens'] = await this.testTool('affine_list_access_tokens', {}); console.log(` ✅ affine_list_access_tokens`); const createResult = await this.testTool('affine_generate_access_token', { name: 'Test Token' }); results['affine_generate_access_token'] = createResult; if (createResult.status === 'success' && createResult.result?.content?.[0]?.text) { const text = createResult.result.content[0].text; const match = text.match(/"id":\s*"([^"]+)"/); if (match) this.tokenId = match[1]; } console.log(` ✅ affine_generate_access_token`); if (this.tokenId) { results['affine_revoke_access_token'] = await this.testTool('affine_revoke_access_token', { id: this.tokenId }); console.log(` ✅ affine_revoke_access_token`); } else { results['affine_revoke_access_token'] = { status: 'skipped', reason: 'No token ID' }; } return results; } async testBlobTools() { console.log('\n📦 BLOB STORAGE (3 tools)'); const results = {}; const uploadResult = await this.testTool('affine_upload_blob', { workspaceId: this.workspaceId || 'test-workspace', content: 'Test file content', filename: 'test.txt' }); results['affine_upload_blob'] = uploadResult; if (uploadResult.status === 'success' && uploadResult.result?.content?.[0]?.text) { const text = uploadResult.result.content[0].text; const match = text.match(/"id":\s*"([^"]+)"/); if (match) this.blobKey = match[1]; } console.log(` ✅ affine_upload_blob`); if (this.blobKey) { results['affine_delete_blob'] = await this.testTool('affine_delete_blob', { workspaceId: this.workspaceId || 'test-workspace', key: this.blobKey }); console.log(` ✅ affine_delete_blob`); } else { results['affine_delete_blob'] = { status: 'skipped', reason: 'No blob key' }; } results['affine_cleanup_blobs'] = await this.testTool('affine_cleanup_blobs', { workspaceId: this.workspaceId || 'test-workspace' }); console.log(` ✅ affine_cleanup_blobs`); return results; } async testNotificationTools() { console.log('\n🔔 NOTIFICATIONS (3 tools)'); const results = {}; const listResult = await this.testTool('affine_list_notifications', {}); results['affine_list_notifications'] = listResult; console.log(` ✅ affine_list_notifications`); if (listResult.status === 'success' && listResult.result?.content?.[0]?.text) { const text = listResult.result.content[0].text; const match = text.match(/"id":\s*"([^"]+)"/); if (match) { results['affine_read_notification'] = await this.testTool('affine_read_notification', { id: match[1] }); console.log(` ✅ affine_read_notification`); } else { results['affine_read_notification'] = { status: 'skipped', reason: 'No notification ID' }; } } else { results['affine_read_notification'] = { status: 'skipped', reason: 'No notifications' }; } results['affine_read_all_notifications'] = await this.testTool('affine_read_all_notifications', {}); console.log(` ✅ affine_read_all_notifications`); return results; } async testAdvancedTools() { console.log('\n⚙️ ADVANCED OPERATIONS (1 tool)'); const results = {}; if (this.docId) { results['affine_apply_doc_updates'] = await this.testTool('affine_apply_doc_updates', { docId: this.docId, op: 'push', updates: [{ update: Buffer.from('test update').toString('base64') }] }); console.log(` ✅ affine_apply_doc_updates`); } else { results['affine_apply_doc_updates'] = { status: 'skipped', reason: 'No doc ID' }; } return results; } async testRealtimeTools() { console.log('\n🔄 REALTIME OPERATIONS (3 tools)'); const results = {}; results['affine_realtime_connect'] = await this.testTool('affine_realtime_connect', { workspaceId: this.workspaceId || 'test-workspace' }); console.log(` ✅ affine_realtime_connect`); const createResult = await this.testTool('affine_realtime_create_doc', { workspaceId: this.workspaceId || 'test-workspace', title: 'Realtime Test Document', content: 'This is a test document created via WebSocket' }); results['affine_realtime_create_doc'] = createResult; console.log(` ✅ affine_realtime_create_doc`); if (createResult.status === 'success' && createResult.result?.content?.[0]?.text) { const text = createResult.result.content[0].text; const match = text.match(/Document ID: ([a-zA-Z0-9-_]+)/); if (match) { results['affine_realtime_update_doc'] = await this.testTool('affine_realtime_update_doc', { workspaceId: this.workspaceId || 'test-workspace', docId: match[1], title: 'Updated Realtime Document', content: 'Updated content via WebSocket' }); console.log(` ✅ affine_realtime_update_doc`); } else { results['affine_realtime_update_doc'] = { status: 'skipped', reason: 'No realtime doc ID' }; } } else { results['affine_realtime_update_doc'] = { status: 'skipped', reason: 'No realtime doc created' }; } return results; } async testExperimentalTools() { console.log('\n🧪 EXPERIMENTAL TOOLS (3 tools)'); const results = {}; results['affine_experimental_create_workspace'] = await this.testTool('affine_experimental_create_workspace', { name: 'Experimental Workspace', initialDocTitle: 'Initial Doc' }); console.log(` ✅ affine_experimental_create_workspace`); results['affine_experimental_init_doc'] = await this.testTool('affine_experimental_init_doc', { workspaceId: this.workspaceId || 'test-workspace', docId: 'test-doc-' + Date.now(), title: 'Experimental Doc', content: 'Experimental content' }); console.log(` ✅ affine_experimental_init_doc`); results['affine_experimental_clone_doc'] = await this.testTool('affine_experimental_clone_doc', { sourceWorkspaceId: this.workspaceId || 'test-workspace', sourceDocId: this.docId || 'test-doc', targetWorkspaceId: this.workspaceId || 'test-workspace', newTitle: 'Cloned Doc' }); console.log(` ✅ affine_experimental_clone_doc`); return results; } async runAllTests() { console.log('🚀 Starting Comprehensive AFFiNE MCP Server Test'); console.log('='.repeat(60)); console.log(`Testing all 50 tools across 11 categories`); console.log('='.repeat(60)); await this.startServer(); console.log('\n✅ MCP Server started successfully\n'); // Run all test categories this.testResults.workspace = await this.testWorkspaceTools(); this.testResults.document = await this.testDocumentTools(); this.testResults.collaboration = await this.testCollaborationTools(); this.testResults.history = await this.testHistoryTools(); this.testResults.user = await this.testUserTools(); this.testResults.accessToken = await this.testAccessTokenTools(); this.testResults.blob = await this.testBlobTools(); this.testResults.notification = await this.testNotificationTools(); this.testResults.advanced = await this.testAdvancedTools(); this.testResults.realtime = await this.testRealtimeTools(); this.testResults.experimental = await this.testExperimentalTools(); this.generateSummary(); if (this.server) { this.server.kill(); } } generateSummary() { console.log('\n' + '='.repeat(60)); console.log('📊 COMPREHENSIVE TEST SUMMARY'); console.log('='.repeat(60)); let totalSuccess = 0; let totalFailed = 0; let totalSkipped = 0; let totalError = 0; // Count all tools Object.entries(TOOL_CATEGORIES).forEach(([category, tools]) => { this.totalTools += tools.length; }); // Analyze results by category Object.entries(this.testResults).forEach(([category, results]) => { const tools = TOOL_CATEGORIES[category]; let catSuccess = 0; let catFailed = 0; let catSkipped = 0; let catError = 0; tools.forEach(tool => { const result = results[tool]; if (!result) { catError++; } else if (result.status === 'success') { catSuccess++; } else if (result.status === 'failed') { catFailed++; } else if (result.status === 'skipped') { catSkipped++; } else if (result.status === 'error') { catError++; } }); totalSuccess += catSuccess; totalFailed += catFailed; totalSkipped += catSkipped; totalError += catError; const catName = category.charAt(0).toUpperCase() + category.slice(1); console.log(`\n${catName} (${tools.length} tools):`); console.log(` ✅ Success: ${catSuccess}`); if (catFailed > 0) console.log(` ❌ Failed: ${catFailed}`); if (catSkipped > 0) console.log(` ⏭️ Skipped: ${catSkipped}`); if (catError > 0) console.log(` ⚠️ Error: ${catError}`); }); console.log('\n' + '-'.repeat(60)); console.log('OVERALL RESULTS:'); console.log(`Total Tools: ${this.totalTools}`); console.log(`✅ Successful: ${totalSuccess}`); console.log(`❌ Failed: ${totalFailed}`); console.log(`⏭️ Skipped: ${totalSkipped}`); console.log(`⚠️ Errors: ${totalError}`); const tested = totalSuccess + totalFailed; const successRate = tested > 0 ? ((totalSuccess / tested) * 100).toFixed(1) : 0; console.log(`\n📈 Success Rate: ${successRate}% (of tested tools)`); console.log(`📊 Coverage: ${((tested / this.totalTools) * 100).toFixed(1)}% (${tested}/${this.totalTools} tools tested)`); // Save detailed results const timestamp = new Date().toISOString(); const resultsFile = `comprehensive-test-results-${timestamp.replace(/[:.]/g, '-')}.json`; fs.writeFileSync(resultsFile, JSON.stringify({ timestamp, summary: { totalTools: this.totalTools, successful: totalSuccess, failed: totalFailed, skipped: totalSkipped, errors: totalError, successRate, coverage: ((tested / this.totalTools) * 100).toFixed(1) }, detailedResults: this.testResults }, null, 2)); console.log(`\n📁 Detailed results saved to: ${resultsFile}`); console.log('='.repeat(60)); } } // Run comprehensive tests const runner = new ComprehensiveTestRunner(); runner.runAllTests().catch(console.error);

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/DAWNCR0W/affine-mcp-server'

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