We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/cogell/mural-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
#!/usr/bin/env node
import { MuralClient } from '../build/mural-client.js';
import 'dotenv/config';
/**
* Adaptive Integration Test for Sticky Note CRUD Operations
*
* This test is flexible and will:
* 1. Try to find "root" workspace, fallback to first available workspace
* 2. Try to find "mural-mcp-test-board", fallback to first available board
* 3. Provide helpful guidance if no suitable test environment is found
* 4. Test the complete CRUD cycle with detailed logging
*/
async function adaptiveIntegrationTest() {
console.log('π― Adaptive Integration Test: Sticky Note CRUD Operations');
console.log('=' .repeat(62));
console.log(`π
Started at: ${new Date().toISOString()}`);
console.log('');
const testState = {
startTime: Date.now(),
client: null,
workspace: null,
board: null,
widgets: [],
phase: 'initialization',
results: {}
};
try {
// Phase 1: Initialize and validate environment
console.log('π Phase 1: Environment Validation');
testState.phase = 'environment_validation';
console.log(' π Checking environment variables...');
const clientId = process.env.MURAL_CLIENT_ID;
const clientSecret = process.env.MURAL_CLIENT_SECRET;
if (!clientId) {
throw new Error('MURAL_CLIENT_ID environment variable is required');
}
console.log(' β
Environment variables present');
console.log(' π Initializing Mural client...');
testState.client = new MuralClient(clientId, clientSecret);
console.log(' π Validating OAuth scopes...');
const scopes = await testState.client.getUserScopes();
const hasRead = scopes.includes('murals:read');
const hasWrite = scopes.includes('murals:write');
console.log(` murals:read: ${hasRead ? 'β
' : 'β'}`);
console.log(` murals:write: ${hasWrite ? 'β
' : 'β'}`);
console.log(` Total scopes: ${scopes.length} (${scopes.join(', ')})`);
if (!hasRead || !hasWrite) {
throw new Error('Both murals:read and murals:write scopes are required. Please re-authenticate with proper scopes.');
}
testState.results.environment = true;
// Phase 2: Discover and select workspace
console.log('\nπ’ Phase 2: Workspace Discovery');
testState.phase = 'workspace_discovery';
console.log(' π Fetching available workspaces...');
const workspaces = await testState.client.getWorkspaces();
console.log(` Found ${workspaces.length} workspace(s):`);
workspaces.forEach((ws, index) => {
console.log(` ${index + 1}. "${ws.name}" (${ws.id})`);
});
// Try to find preferred workspace, fallback to first available
let selectedWorkspace = workspaces.find(ws =>
ws.name.toLowerCase().includes('root') ||
ws.name.toLowerCase().includes('test') ||
ws.name.toLowerCase().includes('dev')
);
if (!selectedWorkspace && workspaces.length > 0) {
selectedWorkspace = workspaces[0];
console.log(` β οΈ No preferred workspace found, using: "${selectedWorkspace.name}"`);
} else if (selectedWorkspace) {
console.log(` β
Using preferred workspace: "${selectedWorkspace.name}"`);
}
if (!selectedWorkspace) {
throw new Error('No workspaces available. Please ensure you have access to at least one workspace.');
}
testState.workspace = selectedWorkspace;
testState.results.workspace = true;
// Phase 3: Discover and select board
console.log('\nπ¨ Phase 3: Board Discovery');
testState.phase = 'board_discovery';
console.log(` π Fetching boards from workspace "${testState.workspace.name}"...`);
const boards = await testState.client.getWorkspaceMurals(testState.workspace.id);
console.log(` Found ${boards.length} board(s):`);
boards.forEach((board, index) => {
console.log(` ${index + 1}. "${board.title}" (${board.id})`);
});
// Try to find preferred test board, fallback to first available
let selectedBoard = boards.find(board =>
board.title.toLowerCase().includes('test') ||
board.title.toLowerCase().includes('mcp') ||
board.title.toLowerCase().includes('integration')
);
if (!selectedBoard && boards.length > 0) {
selectedBoard = boards[0];
console.log(` β οΈ No test board found, using: "${selectedBoard.title}"`);
console.log(' π‘ Tip: Create a board named "mural-mcp-test-board" for dedicated testing');
} else if (selectedBoard) {
console.log(` β
Using test board: "${selectedBoard.title}"`);
}
if (!selectedBoard) {
throw new Error(`No boards available in workspace "${testState.workspace.name}". Please create at least one board.`);
}
testState.board = selectedBoard;
testState.results.board = true;
// Check existing widgets as baseline
const existingWidgets = await testState.client.getMuralWidgets(testState.board.id);
console.log(` π Existing widgets on board: ${existingWidgets.length}`);
// Phase 4: CRUD Operations Test
console.log('\nπ§ͺ Phase 4: CRUD Operations Test');
testState.phase = 'crud_operations';
const testIdentifier = `IntegrationTest_${Date.now()}`;
const testData = {
x: 50 + Math.floor(Math.random() * 200), // Random position to avoid overlap
y: 50 + Math.floor(Math.random() * 200),
text: `Integration Test Sticky Note - ${new Date().toLocaleString()}`,
width: 200,
height: 150,
shape: 'rectangle',
style: {
backgroundColor: '#E6F3FF',
textColor: '#003366',
fontSize: 13
}
};
// 4.1: CREATE
console.log(' π Step 1: CREATE sticky note...');
const createStart = Date.now();
const createdWidgets = await testState.client.createStickyNotes(testState.board.id, [testData]);
if (!createdWidgets || createdWidgets.length === 0) {
throw new Error('Create operation failed - no widgets returned');
}
const createdWidget = createdWidgets[0];
testState.widgets.push(createdWidget);
console.log(` β
CREATE successful (${Date.now() - createStart}ms)`);
console.log(` Widget ID: ${createdWidget.id}`);
console.log(` Text: "${createdWidget.text || testData.text}"`);
console.log(` Position: (${createdWidget.x || testData.x}, ${createdWidget.y || testData.y})`);
testState.results.create = true;
// 4.2: READ
console.log('\n π Step 2: READ sticky note...');
const readStart = Date.now();
const readWidget = await testState.client.getMuralWidget(testState.board.id, createdWidget.id);
console.log(` β
READ successful (${Date.now() - readStart}ms)`);
console.log(` Retrieved text: "${readWidget.text || 'N/A'}"`);
console.log(` Retrieved position: (${readWidget.x || 'N/A'}, ${readWidget.y || 'N/A'})`);
console.log(` Widget type: ${readWidget.type || 'N/A'}`);
// Verify data integrity
const dataIntegrity = {
textMatch: readWidget.text === testData.text,
positionMatch: readWidget.x === testData.x && readWidget.y === testData.y,
idMatch: readWidget.id === createdWidget.id
};
console.log(' π Data integrity check:');
console.log(` Text matches: ${dataIntegrity.textMatch ? 'β
' : 'β'}`);
console.log(` Position matches: ${dataIntegrity.positionMatch ? 'β
' : 'β'}`);
console.log(` ID matches: ${dataIntegrity.idMatch ? 'β
' : 'β'}`);
testState.results.read = true;
// 4.3: UPDATE
console.log('\n βοΈ Step 3: UPDATE sticky note...');
const updateStart = Date.now();
const updatedText = `UPDATED - ${testIdentifier} - ${new Date().toLocaleString()}`;
const updateData = {
text: updatedText,
style: {
backgroundColor: '#FFE6E6',
fontSize: 15
}
};
const updatedWidget = await testState.client.updateStickyNote(
testState.board.id,
createdWidget.id,
updateData
);
console.log(` β
UPDATE successful (${Date.now() - updateStart}ms)`);
console.log(` New text: "${updatedWidget.text || updatedText}"`);
// Verify update by reading again
const verifyRead = await testState.client.getMuralWidget(testState.board.id, createdWidget.id);
const updateVerified = verifyRead.text === updatedText;
console.log(` π Update verification: ${updateVerified ? 'β
' : 'β'}`);
if (!updateVerified) {
console.log(` Expected: "${updatedText}"`);
console.log(` Got: "${verifyRead.text || 'N/A'}"`);
}
testState.results.update = updateVerified;
// 4.4: DELETE
console.log('\n ποΈ Step 4: DELETE sticky note...');
const deleteStart = Date.now();
await testState.client.deleteWidget(testState.board.id, createdWidget.id);
console.log(` β
DELETE operation completed (${Date.now() - deleteStart}ms)`);
// Verify deletion
try {
await testState.client.getMuralWidget(testState.board.id, createdWidget.id);
console.log(' β Widget still exists after deletion!');
testState.results.delete = false;
} catch (error) {
console.log(' β
DELETE verified - widget no longer exists');
testState.results.delete = true;
// Remove from our tracking since it's deleted
testState.widgets = testState.widgets.filter(w => w.id !== createdWidget.id);
}
testState.results.crud = true;
} catch (error) {
console.error(`\nβ Test failed in ${testState.phase} phase: ${error.message}`);
// Provide contextual help based on where the failure occurred
if (testState.phase === 'environment_validation') {
console.log('\nπ‘ Environment Setup Help:');
console.log(' β’ Ensure MURAL_CLIENT_ID is set in your environment');
console.log(' β’ Ensure MURAL_CLIENT_SECRET is set (recommended)');
console.log(' β’ Run authentication: npx @modelcontextprotocol/inspector build/index.js');
console.log(' β’ Verify OAuth scopes include murals:read and murals:write');
} else if (testState.phase === 'workspace_discovery') {
console.log('\nπ‘ Workspace Access Help:');
console.log(' β’ Ensure your OAuth app has access to workspaces');
console.log(' β’ Check that your user account has workspace permissions');
console.log(' β’ Try refreshing your authentication tokens');
} else if (testState.phase === 'board_discovery') {
console.log('\nπ‘ Board Access Help:');
console.log(' β’ Create a board in your workspace for testing');
console.log(' β’ Ensure your user has permissions to the selected workspace');
console.log(' β’ Try using a different workspace');
}
// Attempt cleanup
if (testState.widgets.length > 0 && testState.client && testState.board) {
console.log('\nπ§Ή Attempting cleanup...');
for (const widget of testState.widgets) {
try {
await testState.client.deleteWidget(testState.board.id, widget.id);
console.log(` β
Cleaned up widget ${widget.id}`);
} catch (cleanupError) {
console.log(` β οΈ Failed to cleanup widget ${widget.id}: ${cleanupError.message}`);
}
}
}
}
// Final Results
const totalTime = Date.now() - testState.startTime;
console.log('\n' + '='.repeat(62));
console.log('π ADAPTIVE INTEGRATION TEST RESULTS');
console.log('='.repeat(62));
if (testState.workspace && testState.board) {
console.log('\nπ― Test Environment Used:');
console.log(` Workspace: "${testState.workspace.name}" (${testState.workspace.id})`);
console.log(` Board: "${testState.board.title}" (${testState.board.id})`);
}
console.log('\nπ Test Results:');
const results = testState.results;
console.log(` π§ Environment: ${results.environment ? 'β
PASS' : 'β FAIL'}`);
console.log(` π’ Workspace: ${results.workspace ? 'β
PASS' : 'β FAIL'}`);
console.log(` π¨ Board: ${results.board ? 'β
PASS' : 'β FAIL'}`);
console.log(` π Create: ${results.create ? 'β
PASS' : 'β FAIL'}`);
console.log(` π Read: ${results.read ? 'β
PASS' : 'β FAIL'}`);
console.log(` βοΈ Update: ${results.update ? 'β
PASS' : 'β FAIL'}`);
console.log(` ποΈ Delete: ${results.delete ? 'β
PASS' : 'β FAIL'}`);
const passedTests = Object.values(results).filter(Boolean).length;
const totalTests = Object.keys(results).length;
const successRate = (passedTests / totalTests * 100).toFixed(1);
console.log(`\nπ Overall: ${passedTests}/${totalTests} phases passed (${successRate}%)`);
console.log(`β±οΈ Total time: ${totalTime}ms`);
if (results.crud) {
console.log('\nπ SUCCESS! All CRUD operations completed successfully.');
console.log(' The Mural MCP server is working correctly with your environment.');
console.log('\nπΌ Your test environment:');
console.log(` β’ Workspace: "${testState.workspace?.name}"`);
console.log(` β’ Board: "${testState.board?.title}"`);
console.log('\nπ‘ You can use these for future testing.');
process.exit(0);
} else {
console.log('\nβ οΈ Some operations failed. See details above.');
console.log('\nπ§ Next Steps:');
console.log(' 1. Review the error messages above');
console.log(' 2. Ensure proper authentication and permissions');
console.log(' 3. Try the suggested troubleshooting steps');
console.log(' 4. Re-run this test after making corrections');
process.exit(1);
}
}
adaptiveIntegrationTest();