Skip to main content
Glama

PubNub MCP Server

by pubnub
test.js51.4 kB
#!/usr/bin/env node import assert from 'assert'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; // Terminal colors and styles const RESET = '\x1b[0m'; const BOLD = '\x1b[1m'; const FG_GREEN = '\x1b[32m'; const FG_RED = '\x1b[31m'; const originalLog = console.log.bind(console); const originalError = console.error.bind(console); // Color success messages green and bold console.log = (...args) => { const msg = args.join(' '); if (/successfully|passed/.test(msg)) { originalLog(`${FG_GREEN}${BOLD}${msg}${RESET}`); } else { originalLog(...args); } }; // Color error messages red and bold console.error = (...args) => { const msg = args.join(' '); originalError(`${FG_RED}${BOLD}${msg}${RESET}`); }; async function main() { console.log('Starting MCP server and client...'); const client = new Client({ name: 'test-client', version: '1.0.0' }); const transport = new StdioClientTransport({ command: 'node', args: ['index.js'] }); await client.connect(transport); console.log('Connected to MCP server.'); console.log('Listing available tools...'); const { tools } = await client.listTools(); const toolNames = tools.map((tool) => tool.name); const expectedTools = [ 'read_pubnub_sdk_docs', 'read_pubnub_resources', 'publish_pubnub_message', 'signal_pubnub_message', // new signal tool 'get_pubnub_messages', 'get_pubnub_presence', 'write_pubnub_app', // should expose the app creation instructions tool 'pubnub_subscribe_and_receive_messages', // new subscribe and receive tool 'pubnub_app_context', // new app context tool ]; for (const tool of expectedTools) { assert( toolNames.includes(tool), `Missing expected tool: ${tool}. Available tools: ${toolNames.join(', ')}` ); } console.log('All expected tools are present.'); console.log("Testing 'read_pubnub_resources' tool with document 'how_to_write_a_pubnub_app'..."); const pubnubResourcesResult = await client.callTool({ name: 'read_pubnub_resources', arguments: { document: 'how_to_write_a_pubnub_app' }, }); assert( Array.isArray(pubnubResourcesResult.content) && pubnubResourcesResult.content.length > 0, "'read_pubnub_resources' tool returned no content." ); console.log("'read_pubnub_resources' tool returned content successfully."); console.log("Testing 'read_pubnub_resources' tool with document 'how_to_use_pubnub_presence_best_practices'..."); const presencePracResult = await client.callTool({ name: 'read_pubnub_resources', arguments: { document: 'how_to_use_pubnub_presence_best_practices' }, }); assert( Array.isArray(presencePracResult.content) && presencePracResult.content.length > 0 && presencePracResult.content[0].text.includes('# PubNub Presence Best Practices'), "'read_pubnub_resources' tool did not load how_to_use_pubnub_presence_best_practices.md content." ); console.log("'read_pubnub_resources' tool loaded presence best practices content successfully."); // Test the 'read_pubnub_sdk_docs' tool console.log("Testing 'read_pubnub_sdk_docs' tool..."); const sdkDocsResult = await client.callTool({ name: 'read_pubnub_sdk_docs', arguments: { language: 'javascript', apiReference: 'configuration' }, }); assert( Array.isArray(sdkDocsResult.content) && sdkDocsResult.content.length > 0, "'read_pubnub_sdk_docs' tool returned no content." ); console.log("'read_pubnub_sdk_docs' tool returned content successfully."); // Test the 'publish_pubnub_message' tool console.log("Testing 'publish_pubnub_message' tool..."); const publishResult = await client.callTool({ name: 'publish_pubnub_message', arguments: { channel: 'test-channel', message: 'Hello from test!' }, }); assert( Array.isArray(publishResult.content) && publishResult.content.length > 0 && publishResult.content[0].text.length > 0, "'publish_pubnub_message' tool returned no content." ); console.log("'publish_pubnub_message' tool published message successfully."); // Test the 'signal_pubnub_message' tool console.log("Testing 'signal_pubnub_message' tool..."); const signalResult = await client.callTool({ name: 'signal_pubnub_message', arguments: { channel: 'test-channel', message: 'Hello signal!' }, }); assert( Array.isArray(signalResult.content) && signalResult.content.length > 0 && signalResult.content[0].text.length > 0, "'signal_pubnub_message' tool returned no content." ); console.log("'signal_pubnub_message' tool sent signal successfully."); // Test the 'get_pubnub_messages' tool console.log("Testing 'get_pubnub_messages' tool..."); const getMessagesResult = await client.callTool({ name: 'get_pubnub_messages', arguments: { channels: ['test-channel'] }, }); assert( Array.isArray(getMessagesResult.content) && getMessagesResult.content.length > 0 && getMessagesResult.content[0].text.length > 0, "'get_pubnub_messages' tool returned no content." ); console.log("'get_pubnub_messages' tool returned content successfully."); // Test the 'get_pubnub_presence' tool console.log("Testing 'get_pubnub_presence' tool..."); const presenceResult = await client.callTool({ name: 'get_pubnub_presence', arguments: { channels: ['test-channel'] }, }); assert( Array.isArray(presenceResult.content) && presenceResult.content.length > 0 && presenceResult.content[0].text.length > 0, "'get_pubnub_presence' tool returned no content." ); console.log("'get_pubnub_presence' tool returned content successfully."); // Test the 'write_pubnub_app' tool console.log("Testing 'write_pubnub_app' tool..."); const writeAppResult = await client.callTool({ name: 'write_pubnub_app', arguments: { appType: 'default' }, }); assert( Array.isArray(writeAppResult.content) && writeAppResult.content.length > 0, "'write_pubnub_app' tool returned no content." ); console.log("'write_pubnub_app' tool returned content successfully."); // Additional tests for server.tool arguments and options console.log("Testing 'read_pubnub_sdk_docs' tool default apiReference behavior..."); const sdkDefaultResult = await client.callTool({ name: 'read_pubnub_sdk_docs', arguments: { language: 'javascript' }, }); assert( Array.isArray(sdkDefaultResult.content) && sdkDefaultResult.content.length > 0, "'read_pubnub_sdk_docs' default behavior returned no content." ); console.log("'read_pubnub_sdk_docs' default behavior returned content successfully."); // Test presence best practices inclusion in default javascript behavior console.log("Testing presence best practices inclusion in 'read_pubnub_sdk_docs' tool default javascript behavior..."); assert( sdkDefaultResult.content[0].text.includes('# PubNub Presence Best Practices'), "Expected presence best practices header in default 'read_pubnub_sdk_docs' tool output." ); console.log("Presence best practices content included successfully in default 'read_pubnub_sdk_docs' tool output."); // Test the 'read_pubnub_sdk_docs' tool with 'functions' apiReference console.log("Testing 'read_pubnub_sdk_docs' tool with apiReference 'functions'..."); const sdkFunctionsResult = await client.callTool({ name: 'read_pubnub_sdk_docs', arguments: { language: 'javascript', apiReference: 'functions' }, }); assert( Array.isArray(sdkFunctionsResult.content) && sdkFunctionsResult.content.length > 0, "'read_pubnub_sdk_docs' tool with 'functions' returned no content." ); // Verify that the functions documentation is included assert( sdkFunctionsResult.content[0].text.includes('# PubNub Functions 2.0 Development Guidelines'), "Expected functions documentation header in 'read_pubnub_sdk_docs' tool output." ); console.log("'read_pubnub_sdk_docs' tool with 'functions' returned content successfully."); console.log("Testing 'read_pubnub_sdk_docs' tool with language 'dart' and default apiReference behavior..."); const dartSdkResult = await client.callTool({ name: 'read_pubnub_sdk_docs', arguments: { language: 'dart' }, }); assert( Array.isArray(dartSdkResult.content) && dartSdkResult.content.length > 0, "'read_pubnub_sdk_docs' tool with language 'dart' returned no content." ); assert( dartSdkResult.content[0].text.includes('TITLE: Installing PubNub Dart SDK using Pub'), "Expected Dart SDK installation header in 'read_pubnub_sdk_docs' tool output." ); console.log("'read_pubnub_sdk_docs' tool with language 'dart' returned content successfully."); console.log("Testing 'read_pubnub_sdk_docs' tool with language 'dart' and apiReference 'publish-and-subscribe'..."); const dartPubSubResult = await client.callTool({ name: 'read_pubnub_sdk_docs', arguments: { language: 'dart', apiReference: 'publish-and-subscribe' }, }); assert( Array.isArray(dartPubSubResult.content) && dartPubSubResult.content.length > 0, "'read_pubnub_sdk_docs' tool with language 'dart' and apiReference 'publish-and-subscribe' returned no content." ); console.log("'read_pubnub_sdk_docs' tool with language 'dart' and apiReference 'publish-and-subscribe' returned content successfully."); // Ensure that any "(old)" sections and subsequent content are removed assert( !dartPubSubResult.content[0].text.includes('(old)'), "Expected no '(old)' sections in 'publish-and-subscribe' documentation." ); console.log("'read_pubnub_sdk_docs' tool removed '(old)' sections from 'publish-and-subscribe' docs successfully."); // Test alias 'App Context' for apiReference equals 'objects' console.log("Testing 'read_pubnub_sdk_docs' tool with alias 'App Context' is same as 'objects'..."); const objectsJsResult = await client.callTool({ name: 'read_pubnub_sdk_docs', arguments: { language: 'javascript', apiReference: 'objects' }, }); const appContextJsResult = await client.callTool({ name: 'read_pubnub_sdk_docs', arguments: { language: 'javascript', apiReference: 'App Context' }, }); assert.deepStrictEqual( appContextJsResult.content, objectsJsResult.content, "'App Context' alias did not produce same content as 'objects'" ); console.log("'App Context' alias behaves the same as 'objects' in 'read_pubnub_sdk_docs' tool."); // Test rest-api language special case console.log("Testing 'read_pubnub_sdk_docs' tool with language 'rest-api'..."); const restApiResult = await client.callTool({ name: 'read_pubnub_sdk_docs', arguments: { language: 'rest-api', apiReference: 'configuration' }, }); assert( Array.isArray(restApiResult.content) && restApiResult.content.length > 0, "'read_pubnub_sdk_docs' tool with language 'rest-api' returned no content." ); // Verify that rest-api returns content regardless of apiReference const restApiContent = restApiResult.content[0].text; assert( restApiContent.length > 0, "REST API content should not be empty." ); // Test that different apiReference values return the same content for rest-api console.log("Testing 'read_pubnub_sdk_docs' tool with language 'rest-api' and different apiReference..."); const restApiResult2 = await client.callTool({ name: 'read_pubnub_sdk_docs', arguments: { language: 'rest-api', apiReference: 'publish-and-subscribe' }, }); assert( Array.isArray(restApiResult2.content) && restApiResult2.content.length > 0, "'read_pubnub_sdk_docs' tool with language 'rest-api' and different apiReference returned no content." ); // Verify both calls return the same content (since rest-api ignores apiReference) assert.deepStrictEqual( restApiResult.content, restApiResult2.content, "REST API should return same content regardless of apiReference parameter" ); console.log("'read_pubnub_sdk_docs' tool with language 'rest-api' returned content successfully and ignores apiReference as expected."); console.log("Testing 'read_pubnub_sdk_docs' tool for language 'java' and all apiReference options..."); const javaApiReferences = [ 'configuration', 'publish-and-subscribe', 'presence', 'access-manager', 'channel-groups', 'storage-and-playback', 'mobile-push', 'objects', 'files', 'message-actions', 'misc', 'functions', ]; for (const apiReference of javaApiReferences) { console.log(`Testing 'read_pubnub_sdk_docs' tool with language 'java', apiReference '${apiReference}'...`); const javaResult = await client.callTool({ name: 'read_pubnub_sdk_docs', arguments: { language: 'java', apiReference }, }); assert( Array.isArray(javaResult.content) && javaResult.content.length > 0, `'read_pubnub_sdk_docs' tool returned no content for java ${apiReference}` ); if (apiReference === 'functions') { assert( javaResult.content[0].text.includes('# PubNub Functions 2.0 Development Guidelines'), "Expected functions documentation header in 'read_pubnub_sdk_docs' tool output for java" ); } } console.log("All 'read_pubnub_sdk_docs' tests for language 'java' passed successfully."); console.log("Testing 'read_pubnub_chat_sdk_docs' tool for all chat SDK docs..."); { const { readFile } = await import('fs/promises'); const data = await readFile('chat_sdk_urls.txt', 'utf8'); const lines = data.split(/\r?\n/); const chatSdkUrlsMap = {}; let currentLang = null; for (const line of lines) { const trimmed = line.trim(); if (!trimmed) { currentLang = null; } else if (!currentLang) { currentLang = trimmed; chatSdkUrlsMap[currentLang] = []; } else { chatSdkUrlsMap[currentLang].push(trimmed); } } for (const [lang, urls] of Object.entries(chatSdkUrlsMap)) { const languageArg = lang.toLowerCase(); for (const url of urls) { const topic = url.split('/').pop(); console.log(`Testing 'read_pubnub_chat_sdk_docs' for language '${languageArg}', topic '${topic}'...`); const result = await client.callTool({ name: 'read_pubnub_chat_sdk_docs', arguments: { language: languageArg, topic }, }); assert( Array.isArray(result.content) && result.content.length > 0, `'read_pubnub_chat_sdk_docs' returned no content for ${languageArg} ${topic}` ); } } console.log("All 'read_pubnub_chat_sdk_docs' tests passed successfully."); } console.log("Testing 'read_pubnub_resources' tool with document 'pubnub_concepts'..."); const conceptsResult = await client.callTool({ name: 'read_pubnub_resources', arguments: { document: 'pubnub_concepts' }, }); assert( Array.isArray(conceptsResult.content) && conceptsResult.content.length > 0, "'read_pubnub_resources' with 'pubnub_concepts' returned no content." ); console.log("'read_pubnub_resources' returned concepts content successfully."); // Test for chat_sdk resource console.log("Testing 'read_pubnub_resources' tool with document 'chat_sdk'..."); const chatSdkResult = await client.callTool({ name: 'read_pubnub_resources', arguments: { document: 'chat_sdk' }, }); assert( Array.isArray(chatSdkResult.content) && chatSdkResult.content.length > 0, "'read_pubnub_resources' with 'chat_sdk' returned no content." ); assert( chatSdkResult.content[0].text.includes('Sample chat app'), "Expected 'Sample chat app' in 'chat_sdk' result" ); console.log("'read_pubnub_resources' tool with 'chat_sdk' returned content successfully."); // Verify new examples (user creation note, channel helper, logout, reactions) present in chat_sdk guide assert( chatSdkResult.content[0].text.includes('**Note:** The Chat SDK requires you to explicitly create or retrieve users'), "Expected user creation note in 'chat_sdk' result" ); assert( chatSdkResult.content[0].text.includes('async function getOrCreateDirectChannel'), "Expected getOrCreateDirectChannel helper in 'chat_sdk' result" ); assert( chatSdkResult.content[0].text.includes('async function logout(chat)'), "Expected logout cleanup snippet in 'chat_sdk' result" ); assert( chatSdkResult.content[0].text.includes('async function toggleReaction'), "Expected reaction toggle snippet in 'chat_sdk' result" ); console.log("New examples (user creation, channel helper, logout, reactions) present in 'chat_sdk' guide."); // Test for pubnub_chat_sdk resource console.log("Testing 'read_pubnub_resources' tool with document 'pubnub_chat_sdk'..."); const pubnubChatSdkResult = await client.callTool({ name: 'read_pubnub_resources', arguments: { document: 'pubnub_chat_sdk' }, }); assert( Array.isArray(pubnubChatSdkResult.content) && pubnubChatSdkResult.content.length > 0, "'read_pubnub_resources' with 'pubnub_chat_sdk' returned no content." ); assert( pubnubChatSdkResult.content[0].text.includes('Sample chat app'), "Expected 'Sample chat app' in 'pubnub_chat_sdk' result" ); console.log("'read_pubnub_resources' tool with 'pubnub_chat_sdk' returned content successfully."); // Verify new examples (user creation note, channel helper, logout, reactions) present in pubnub_chat_sdk guide assert( pubnubChatSdkResult.content[0].text.includes('**Note:** The Chat SDK requires you to explicitly create or retrieve users'), "Expected user creation note in 'pubnub_chat_sdk' result" ); assert( pubnubChatSdkResult.content[0].text.includes('async function getOrCreateDirectChannel'), "Expected getOrCreateDirectChannel helper in 'pubnub_chat_sdk' result" ); assert( pubnubChatSdkResult.content[0].text.includes('async function logout(chat)'), "Expected logout cleanup snippet in 'pubnub_chat_sdk' result" ); assert( pubnubChatSdkResult.content[0].text.includes('async function toggleReaction'), "Expected reaction toggle snippet in 'pubnub_chat_sdk' result" ); console.log("New examples (user creation, channel helper, logout, reactions) present in 'pubnub_chat_sdk' guide."); console.log("Testing 'read_pubnub_resources' tool with document 'how_to_use_app_context_objects_with_dart'..."); const dartAppContextResult = await client.callTool({ name: 'read_pubnub_resources', arguments: { document: 'how_to_use_app_context_objects_with_dart' }, }); assert( Array.isArray(dartAppContextResult.content) && dartAppContextResult.content.length > 0, "'read_pubnub_resources' with 'how_to_use_app_context_objects_with_dart' returned no content." ); assert( dartAppContextResult.content[0].text.includes('App Context'), "Expected 'App Context' in 'how_to_use_app_context_objects_with_dart' result" ); console.log("'read_pubnub_resources' tool with 'how_to_use_app_context_objects_with_dart' returned content successfully."); console.log("Testing 'read_pubnub_resources' tool with document 'dart'..."); const dartLangResult = await client.callTool({ name: 'read_pubnub_resources', arguments: { document: 'dart' }, }); assert( Array.isArray(dartLangResult.content) && dartLangResult.content.length > 0, "'read_pubnub_resources' with 'dart' returned no content." ); assert( dartLangResult.content[0].text.includes('TITLE: Installing PubNub Dart SDK using Pub'), "Expected Dart language resource header in 'read_pubnub_resources' tool output." ); console.log("'read_pubnub_resources' tool with 'dart' returned content successfully."); console.log("Testing 'read_pubnub_resources' tool with document 'how_to_use_channel_filters_subscribe_filters_and_message_filters'..."); const channelFiltersResult = await client.callTool({ name: 'read_pubnub_resources', arguments: { document: 'how_to_use_channel_filters_subscribe_filters_and_message_filters' }, }); assert( Array.isArray(channelFiltersResult.content) && channelFiltersResult.content.length > 0, "'read_pubnub_resources' with 'how_to_use_channel_filters_subscribe_filters_and_message_filters' returned no content." ); assert( channelFiltersResult.content[0].text.includes('Subscribe Filters'), "Expected 'Subscribe Filters' in 'how_to_use_channel_filters_subscribe_filters_and_message_filters' result" ); assert( channelFiltersResult.content[0].text.includes('Message Filters'), "Expected 'Message Filters' in 'how_to_use_channel_filters_subscribe_filters_and_message_filters' result" ); assert( channelFiltersResult.content[0].text.includes('What are Subscribe Filters'), "Expected 'What are Subscribe Filters' in 'how_to_use_channel_filters_subscribe_filters_and_message_filters' result" ); assert( channelFiltersResult.content[0].text.includes('meta.priority == "high"'), "Expected filter expression example in 'how_to_use_channel_filters_subscribe_filters_and_message_filters' result" ); console.log("'read_pubnub_resources' tool with 'how_to_use_channel_filters_subscribe_filters_and_message_filters' returned content successfully."); console.log("Testing 'read_pubnub_resources' tool with document 'how_to_use_pubnub_events_and_actions'..."); const eventsActionsResult = await client.callTool({ name: 'read_pubnub_resources', arguments: { document: 'how_to_use_pubnub_events_and_actions' }, }); assert( Array.isArray(eventsActionsResult.content) && eventsActionsResult.content.length > 0, "'read_pubnub_resources' with 'how_to_use_pubnub_events_and_actions' returned no content." ); assert( eventsActionsResult.content[0].text.includes('PubNub Events & Actions Guide'), "Expected 'PubNub Events & Actions Guide' in 'how_to_use_pubnub_events_and_actions' result" ); assert( eventsActionsResult.content[0].text.includes('Events & Actions'), "Expected 'Events & Actions' content in 'how_to_use_pubnub_events_and_actions' result" ); assert( eventsActionsResult.content[0].text.length > 1000, "Expected substantial content (>1000 characters) in 'how_to_use_pubnub_events_and_actions' result" ); console.log("'read_pubnub_resources' tool with 'how_to_use_pubnub_events_and_actions' returned content successfully."); // Test error handling for 'read_pubnub_resources' tool with invalid document console.log("Testing 'read_pubnub_resources' tool with invalid document..."); try { await client.callTool({ name: 'read_pubnub_resources', arguments: { document: 'nonexistent_doc' }, }); assert(false, "Expected 'read_pubnub_resources' with invalid document to throw an error."); } catch (err) { assert( err.message.includes('Invalid arguments for tool read_pubnub_resources'), `Unexpected error for invalid document: ${err.message}` ); } console.log("'read_pubnub_resources' invalid document error handling works successfully."); // Additional test for 'get_pubnub_messages' tool with multiple channels console.log("Testing 'get_pubnub_messages' tool with multiple channels..."); const multiMessagesResult = await client.callTool({ name: 'get_pubnub_messages', arguments: { channels: ['test-channel', 'another-channel'] }, }); assert( Array.isArray(multiMessagesResult.content) && multiMessagesResult.content.length > 0 && multiMessagesResult.content[0].text.length > 0, "'get_pubnub_messages' with multiple channels returned no content." ); console.log("'get_pubnub_messages' multiple channels returned content successfully."); // Test 'get_pubnub_messages' tool with pagination parameters console.log("Testing 'get_pubnub_messages' tool with pagination parameters (count)..."); const paginatedMessagesResult = await client.callTool({ name: 'get_pubnub_messages', arguments: { channels: ['test-channel'], count: 5 }, }); assert( Array.isArray(paginatedMessagesResult.content) && paginatedMessagesResult.content.length > 0 && paginatedMessagesResult.content[0].text.length > 0, "'get_pubnub_messages' with count parameter returned no content." ); console.log("'get_pubnub_messages' with count parameter returned content successfully."); // Test 'get_pubnub_messages' tool with start timetoken console.log("Testing 'get_pubnub_messages' tool with start timetoken..."); const startTimeMessagesResult = await client.callTool({ name: 'get_pubnub_messages', arguments: { channels: ['test-channel'], start: '15000000000000000' }, }); assert( Array.isArray(startTimeMessagesResult.content) && startTimeMessagesResult.content.length > 0 && startTimeMessagesResult.content[0].text.length > 0, "'get_pubnub_messages' with start timetoken returned no content." ); console.log("'get_pubnub_messages' with start timetoken returned content successfully."); // Test 'get_pubnub_messages' tool with end timetoken console.log("Testing 'get_pubnub_messages' tool with end timetoken..."); const endTimeMessagesResult = await client.callTool({ name: 'get_pubnub_messages', arguments: { channels: ['test-channel'], end: '17000000000000000' }, }); assert( Array.isArray(endTimeMessagesResult.content) && endTimeMessagesResult.content.length > 0 && endTimeMessagesResult.content[0].text.length > 0, "'get_pubnub_messages' with end timetoken returned no content." ); console.log("'get_pubnub_messages' with end timetoken returned content successfully."); // Test 'get_pubnub_messages' tool with all pagination parameters console.log("Testing 'get_pubnub_messages' tool with all pagination parameters..."); const fullPaginationResult = await client.callTool({ name: 'get_pubnub_messages', arguments: { channels: ['test-channel'], start: '15000000000000000', end: '17000000000000000', count: 10 }, }); assert( Array.isArray(fullPaginationResult.content) && fullPaginationResult.content.length > 0 && fullPaginationResult.content[0].text.length > 0, "'get_pubnub_messages' with all pagination parameters returned no content." ); console.log("'get_pubnub_messages' with all pagination parameters returned content successfully."); // Test publishing and retrieving a specific message console.log("Testing publish and retrieve workflow with pagination..."); const randomChannel = `test-channel-${Date.now()}-${Math.floor(Math.random() * 10000)}`; const testMessage = { text: `Test message at ${new Date().toISOString()}`, random: Math.random(), testId: 'pagination-test' }; // Publish a message console.log(`Publishing message to channel '${randomChannel}'...`); const publishTestResult = await client.callTool({ name: 'publish_pubnub_message', arguments: { channel: randomChannel, message: JSON.stringify(testMessage) }, }); assert( Array.isArray(publishTestResult.content) && publishTestResult.content.length > 0 && publishTestResult.content[0].text.includes('Message published successfully'), "Failed to publish test message." ); // Extract timetoken from publish result const publishResponse = publishTestResult.content[0].text; const timetokenMatch = publishResponse.match(/Timetoken: (\d+)/); const publishedTimetoken = timetokenMatch ? timetokenMatch[1] : null; console.log(`Message published with timetoken: ${publishedTimetoken}`); // Sleep for 2 seconds to ensure message is stored console.log("Waiting 2 seconds for message to be stored..."); await new Promise(resolve => setTimeout(resolve, 2000)); // Retrieve the specific message using pagination console.log(`Retrieving message from channel '${randomChannel}'...`); const retrieveResult = await client.callTool({ name: 'get_pubnub_messages', arguments: { channels: [randomChannel], count: 10 }, }); assert( Array.isArray(retrieveResult.content) && retrieveResult.content.length > 0, "Failed to retrieve messages." ); // Parse the response and verify our message is there const messagesData = JSON.parse(retrieveResult.content[0].text); const channelMessages = messagesData.channels[randomChannel]; assert( Array.isArray(channelMessages) && channelMessages.length > 0, `No messages found in channel '${randomChannel}'.` ); // Find our specific message const foundMessage = channelMessages.find(msg => { try { const parsed = typeof msg.message === 'string' ? JSON.parse(msg.message) : msg.message; return parsed.testId === 'pagination-test'; } catch { return false; } }); assert( foundMessage !== undefined, "Could not find the published test message in retrieved messages." ); console.log("Successfully published and retrieved the same message!"); // Test retrieving with timetoken range if (publishedTimetoken) { console.log("Testing retrieval with timetoken range..."); const startTimetoken = (BigInt(publishedTimetoken) - BigInt(1000)).toString(); const endTimetoken = (BigInt(publishedTimetoken) + BigInt(1000)).toString(); const rangeResult = await client.callTool({ name: 'get_pubnub_messages', arguments: { channels: [randomChannel], start: startTimetoken, end: endTimetoken }, }); assert( Array.isArray(rangeResult.content) && rangeResult.content.length > 0, "Failed to retrieve messages with timetoken range." ); const rangeData = JSON.parse(rangeResult.content[0].text); const rangeMessages = rangeData.channels[randomChannel]; assert( Array.isArray(rangeMessages) && rangeMessages.length > 0, "No messages found in timetoken range." ); console.log("Successfully retrieved message using timetoken range!"); } // Test the new 'pubnub_subscribe_and_receive_messages' tool console.log("Testing 'pubnub_subscribe_and_receive_messages' tool..."); const subscribeTestChannel = `subscribe-test-${Date.now()}-${Math.floor(Math.random() * 10000)}`; const subscribeTestMessage = { text: "Test message for subscription", timestamp: new Date().toISOString(), testType: "subscription-test" }; // Start the subscription with a timeout (this will run in background) console.log(`Starting subscription to channel '${subscribeTestChannel}' with 5-second timeout...`); const subscriptionPromise = client.callTool({ name: 'pubnub_subscribe_and_receive_messages', arguments: { channel: subscribeTestChannel, messageCount: 1, timeout: 5000 } }); // Wait a moment, then publish a message console.log("Waiting 1 second before publishing message..."); await new Promise(resolve => setTimeout(resolve, 1000)); console.log(`Publishing message to channel '${subscribeTestChannel}'...`); const subscribePublishResult = await client.callTool({ name: 'publish_pubnub_message', arguments: { channel: subscribeTestChannel, message: JSON.stringify(subscribeTestMessage) } }); assert( Array.isArray(subscribePublishResult.content) && subscribePublishResult.content.length > 0 && subscribePublishResult.content[0].text.includes('Message published successfully'), "Failed to publish message for subscription test." ); // Wait for the subscription to receive the message console.log("Waiting for subscription to receive the message..."); const subscribeResult = await subscriptionPromise; assert( Array.isArray(subscribeResult.content) && subscribeResult.content.length > 0, "'pubnub_subscribe_and_receive_messages' returned no content." ); // Parse and verify the received message const receivedData = JSON.parse(subscribeResult.content[0].text); assert( receivedData.channel === subscribeTestChannel, `Expected channel '${subscribeTestChannel}', got '${receivedData.channel}'` ); assert( receivedData.messageCount === 1, `Expected messageCount 1, got ${receivedData.messageCount}` ); assert( Array.isArray(receivedData.messages) && receivedData.messages.length === 1, "Expected exactly 1 message in the received data" ); // Verify the message content const receivedMessage = receivedData.messages[0]; const parsedMessage = typeof receivedMessage.message === 'string' ? JSON.parse(receivedMessage.message) : receivedMessage.message; assert( parsedMessage.testType === "subscription-test", "Received message does not match the published message" ); console.log("'pubnub_subscribe_and_receive_messages' successfully received the published message!"); // Test with multiple messages console.log("Testing 'pubnub_subscribe_and_receive_messages' with multiple messages..."); const multiSubscribeChannel = `multi-subscribe-test-${Date.now()}-${Math.floor(Math.random() * 10000)}`; const messageCount = 3; // Start subscription for multiple messages with timeout console.log(`Starting subscription for ${messageCount} messages with 10-second timeout...`); const multiSubscriptionPromise = client.callTool({ name: 'pubnub_subscribe_and_receive_messages', arguments: { channel: multiSubscribeChannel, messageCount: messageCount, timeout: 10000 } }); // Wait a moment, then publish multiple messages await new Promise(resolve => setTimeout(resolve, 1000)); for (let i = 1; i <= messageCount; i++) { console.log(`Publishing message ${i}/${messageCount}...`); const multiMessage = { text: `Message ${i} of ${messageCount}`, messageNumber: i, testType: "multi-subscription-test" }; await client.callTool({ name: 'publish_pubnub_message', arguments: { channel: multiSubscribeChannel, message: JSON.stringify(multiMessage) } }); // Small delay between messages if (i < messageCount) { await new Promise(resolve => setTimeout(resolve, 500)); } } // Wait for the subscription to receive all messages console.log(`Waiting for subscription to receive all ${messageCount} messages...`); const multiSubscribeResult = await multiSubscriptionPromise; assert( Array.isArray(multiSubscribeResult.content) && multiSubscribeResult.content.length > 0, "'pubnub_subscribe_and_receive_messages' with multiple messages returned no content." ); // Parse and verify the received messages const multiReceivedData = JSON.parse(multiSubscribeResult.content[0].text); assert( multiReceivedData.channel === multiSubscribeChannel, `Expected channel '${multiSubscribeChannel}', got '${multiReceivedData.channel}'` ); assert( multiReceivedData.messageCount === messageCount, `Expected messageCount ${messageCount}, got ${multiReceivedData.messageCount}` ); assert( Array.isArray(multiReceivedData.messages) && multiReceivedData.messages.length === messageCount, `Expected exactly ${messageCount} messages in the received data, got ${multiReceivedData.messages.length}` ); console.log(`'pubnub_subscribe_and_receive_messages' successfully received all ${messageCount} messages!`); // Test timeout behavior console.log("Testing 'pubnub_subscribe_and_receive_messages' timeout behavior..."); const timeoutChannel = `timeout-test-${Date.now()}-${Math.floor(Math.random() * 10000)}`; const timeoutResult = await client.callTool({ name: 'pubnub_subscribe_and_receive_messages', arguments: { channel: timeoutChannel, messageCount: 1, timeout: 2000 // 2 second timeout with no messages published } }); assert( Array.isArray(timeoutResult.content) && timeoutResult.content.length > 0 && timeoutResult.content[0].text.includes('Timeout'), "'pubnub_subscribe_and_receive_messages' did not handle timeout correctly." ); console.log("'pubnub_subscribe_and_receive_messages' timeout behavior works correctly!"); // Additional test for 'get_pubnub_presence' tool with channelGroups option console.log("Testing 'get_pubnub_presence' tool with channelGroups option..."); const presenceCgResult = await client.callTool({ name: 'get_pubnub_presence', arguments: { channelGroups: ['test-channel-group'] }, }); assert( Array.isArray(presenceCgResult.content) && presenceCgResult.content.length > 0 && presenceCgResult.content[0].text.length > 0, "'get_pubnub_presence' with channelGroups returned no content." ); console.log("'get_pubnub_presence' channelGroups returned content successfully."); // Invalid argument tests console.log("Testing 'get_pubnub_messages' tool with empty channels (should error)..."); try { await client.callTool({ name: 'get_pubnub_messages', arguments: { channels: [] }, }); assert(false, "Expected 'get_pubnub_messages' with empty channels to throw an error."); } catch (err) { assert( err.message.includes('Invalid arguments for tool get_pubnub_messages'), `Unexpected error for empty channels: ${err.message}` ); } console.log("'get_pubnub_messages' empty channels error handling works successfully."); console.log("Testing 'write_pubnub_app' tool with invalid appType (should error)..."); try { await client.callTool({ name: 'write_pubnub_app', arguments: { appType: 'chat' }, }); assert(false, "Expected 'write_pubnub_app' with invalid appType to throw an error."); } catch (err) { assert( err.message.includes('Invalid arguments for tool write_pubnub_app'), `Unexpected error for invalid appType: ${err.message}` ); } console.log("'write_pubnub_app' invalid appType error handling works successfully."); console.log("Testing 'read_pubnub_sdk_docs' tool with invalid language (should error)..."); try { await client.callTool({ name: 'read_pubnub_sdk_docs', arguments: { language: 'haskell', apiReference: 'configuration' }, }); assert(false, "Expected 'read_pubnub_sdk_docs' with invalid language to throw an error."); } catch (err) { assert( err.message.includes('Invalid arguments for tool read_pubnub_sdk_docs'), `Unexpected error for invalid language: ${err.message}` ); } console.log("'read_pubnub_sdk_docs' invalid language error handling works successfully."); console.log("Testing 'read_pubnub_sdk_docs' tool with missing language (should error)..."); try { await client.callTool({ name: 'read_pubnub_sdk_docs', arguments: { apiReference: 'configuration' }, }); assert(false, "Expected 'read_pubnub_sdk_docs' missing language to throw an error."); } catch (err) { assert( err.message.includes('Invalid arguments for tool read_pubnub_sdk_docs'), `Unexpected error for missing language: ${err.message}` ); } console.log("'read_pubnub_sdk_docs' missing language error handling works successfully."); // Test the new 'pubnub_app_context' tool console.log("Testing 'pubnub_app_context' tool with user operations..."); // Generate unique test identifiers const testUserId = `test-user-${Date.now()}-${Math.floor(Math.random() * 10000)}`; const testChannelId = `test-channel-${Date.now()}-${Math.floor(Math.random() * 10000)}`; // Test user creation (set operation) console.log(`Creating user '${testUserId}'...`); const createUserResult = await client.callTool({ name: 'pubnub_app_context', arguments: { type: 'user', operation: 'set', id: testUserId, data: { name: 'Test User', email: 'test@example.com', externalId: 'ext-123', profileUrl: 'https://example.com/avatar.jpg', custom: { department: 'Engineering', role: 'Developer', level: 'Senior' } } } }); assert( Array.isArray(createUserResult.content) && createUserResult.content.length > 0, "'pubnub_app_context' user creation returned no content." ); const createdUser = JSON.parse(createUserResult.content[0].text); assert( createdUser && createdUser.data && createdUser.data.name === 'Test User', "Created user data does not match expected values." ); console.log("User creation test passed successfully."); // Test user retrieval (get operation) console.log(`Retrieving user '${testUserId}'...`); const getUserResult = await client.callTool({ name: 'pubnub_app_context', arguments: { type: 'user', operation: 'get', id: testUserId } }); assert( Array.isArray(getUserResult.content) && getUserResult.content.length > 0, "'pubnub_app_context' user retrieval returned no content." ); const retrievedUser = JSON.parse(getUserResult.content[0].text); assert( retrievedUser && retrievedUser.data && retrievedUser.data.name === 'Test User', "Retrieved user data does not match expected values." ); console.log("User retrieval test passed successfully."); // Test user update console.log(`Updating user '${testUserId}'...`); const updateUserResult = await client.callTool({ name: 'pubnub_app_context', arguments: { type: 'user', operation: 'set', id: testUserId, data: { name: 'Updated Test User', email: 'updated@example.com', custom: { department: 'Product', role: 'Manager', level: 'Senior' } } } }); assert( Array.isArray(updateUserResult.content) && updateUserResult.content.length > 0, "'pubnub_app_context' user update returned no content." ); const updatedUser = JSON.parse(updateUserResult.content[0].text); assert( updatedUser && updatedUser.data && updatedUser.data.name === 'Updated Test User', "Updated user data does not match expected values." ); console.log("User update test passed successfully."); // Test channel creation (set operation) console.log(`Creating channel '${testChannelId}'...`); const createChannelResult = await client.callTool({ name: 'pubnub_app_context', arguments: { type: 'channel', operation: 'set', id: testChannelId, data: { name: 'Test Channel', description: 'A test channel for automated testing', custom: { category: 'testing', public: true, maxMembers: 100 } } } }); assert( Array.isArray(createChannelResult.content) && createChannelResult.content.length > 0, "'pubnub_app_context' channel creation returned no content." ); const createdChannel = JSON.parse(createChannelResult.content[0].text); assert( createdChannel && createdChannel.data && createdChannel.data.name === 'Test Channel', "Created channel data does not match expected values." ); console.log("Channel creation test passed successfully."); // Test channel retrieval (get operation) console.log(`Retrieving channel '${testChannelId}'...`); const getChannelResult = await client.callTool({ name: 'pubnub_app_context', arguments: { type: 'channel', operation: 'get', id: testChannelId } }); assert( Array.isArray(getChannelResult.content) && getChannelResult.content.length > 0, "'pubnub_app_context' channel retrieval returned no content." ); const retrievedChannel = JSON.parse(getChannelResult.content[0].text); assert( retrievedChannel && retrievedChannel.data && retrievedChannel.data.name === 'Test Channel', "Retrieved channel data does not match expected values." ); console.log("Channel retrieval test passed successfully."); // Test membership creation (set operation) console.log(`Creating membership for user '${testUserId}' in channel '${testChannelId}'...`); const createMembershipResult = await client.callTool({ name: 'pubnub_app_context', arguments: { type: 'membership', operation: 'set', id: testUserId, data: { channels: [ { id: testChannelId, custom: { role: 'admin', joinedAt: new Date().toISOString() } } ] } } }); assert( Array.isArray(createMembershipResult.content) && createMembershipResult.content.length > 0, "'pubnub_app_context' membership creation returned no content." ); const createdMembership = JSON.parse(createMembershipResult.content[0].text); assert( createdMembership && createdMembership.data && Array.isArray(createdMembership.data), "Created membership data is not in expected format." ); console.log("Membership creation test passed successfully."); // Test membership retrieval (get operation) console.log(`Retrieving memberships for user '${testUserId}'...`); const getMembershipResult = await client.callTool({ name: 'pubnub_app_context', arguments: { type: 'membership', operation: 'get', id: testUserId } }); assert( Array.isArray(getMembershipResult.content) && getMembershipResult.content.length > 0, "'pubnub_app_context' membership retrieval returned no content." ); const retrievedMembership = JSON.parse(getMembershipResult.content[0].text); assert( retrievedMembership && retrievedMembership.data && Array.isArray(retrievedMembership.data), "Retrieved membership data is not in expected format." ); console.log("Membership retrieval test passed successfully."); // Test channel members retrieval (getAll operation for memberships) console.log(`Retrieving members of channel '${testChannelId}'...`); const getChannelMembersResult = await client.callTool({ name: 'pubnub_app_context', arguments: { type: 'membership', operation: 'getAll', id: testChannelId } }); assert( Array.isArray(getChannelMembersResult.content) && getChannelMembersResult.content.length > 0, "'pubnub_app_context' channel members retrieval returned no content." ); const channelMembers = JSON.parse(getChannelMembersResult.content[0].text); assert( channelMembers && channelMembers.data && Array.isArray(channelMembers.data), "Channel members data is not in expected format." ); console.log("Channel members retrieval test passed successfully."); // Test getAll operations console.log("Testing 'pubnub_app_context' getAll operations..."); // Test get all users const getAllUsersResult = await client.callTool({ name: 'pubnub_app_context', arguments: { type: 'user', operation: 'getAll', id: '', // Not used for getAll options: { limit: 10, includeTotalCount: true } } }); assert( Array.isArray(getAllUsersResult.content) && getAllUsersResult.content.length > 0, "'pubnub_app_context' get all users returned no content." ); const allUsers = JSON.parse(getAllUsersResult.content[0].text); assert( allUsers && allUsers.data && Array.isArray(allUsers.data), "Get all users data is not in expected format." ); console.log("Get all users test passed successfully."); // Test get all channels const getAllChannelsResult = await client.callTool({ name: 'pubnub_app_context', arguments: { type: 'channel', operation: 'getAll', id: '', // Not used for getAll options: { limit: 10, includeTotalCount: true } } }); assert( Array.isArray(getAllChannelsResult.content) && getAllChannelsResult.content.length > 0, "'pubnub_app_context' get all channels returned no content." ); const allChannels = JSON.parse(getAllChannelsResult.content[0].text); assert( allChannels && allChannels.data && Array.isArray(allChannels.data), "Get all channels data is not in expected format." ); console.log("Get all channels test passed successfully."); // Test filtering and sorting options console.log("Testing 'pubnub_app_context' with filtering and sorting options..."); const filteredUsersResult = await client.callTool({ name: 'pubnub_app_context', arguments: { type: 'user', operation: 'getAll', id: '', options: { filter: `name LIKE "*Test*"`, sort: { name: 'asc' }, limit: 5 } } }); assert( Array.isArray(filteredUsersResult.content) && filteredUsersResult.content.length > 0, "'pubnub_app_context' filtered users returned no content." ); console.log("Filtering and sorting test passed successfully."); // Clean up test data console.log("Cleaning up test data..."); // Remove membership first await client.callTool({ name: 'pubnub_app_context', arguments: { type: 'membership', operation: 'remove', id: testUserId, data: { channels: [{ id: testChannelId }] } } }); // Remove user await client.callTool({ name: 'pubnub_app_context', arguments: { type: 'user', operation: 'remove', id: testUserId } }); // Remove channel await client.callTool({ name: 'pubnub_app_context', arguments: { type: 'channel', operation: 'remove', id: testChannelId } }); console.log("Test data cleanup completed successfully."); // Test error handling for App Context tool console.log("Testing 'pubnub_app_context' error handling..."); // Test invalid type try { await client.callTool({ name: 'pubnub_app_context', arguments: { type: 'invalid_type', operation: 'get', id: 'test' } }); assert(false, "Expected 'pubnub_app_context' with invalid type to throw an error."); } catch (err) { assert( err.message.includes('Invalid arguments for tool pubnub_app_context'), `Unexpected error for invalid type: ${err.message}` ); } // Test invalid operation try { await client.callTool({ name: 'pubnub_app_context', arguments: { type: 'user', operation: 'invalid_operation', id: 'test' } }); assert(false, "Expected 'pubnub_app_context' with invalid operation to throw an error."); } catch (err) { assert( err.message.includes('Invalid arguments for tool pubnub_app_context'), `Unexpected error for invalid operation: ${err.message}` ); } // Test missing required parameters try { await client.callTool({ name: 'pubnub_app_context', arguments: { type: 'user', operation: 'get' // Missing id parameter } }); assert(false, "Expected 'pubnub_app_context' with missing id to throw an error."); } catch (err) { assert( err.message.includes('Invalid arguments for tool pubnub_app_context'), `Unexpected error for missing id: ${err.message}` ); } console.log("'pubnub_app_context' error handling tests passed successfully."); console.log('All tests passed.'); process.exit(0); } main().catch((err) => { console.error('Test failed:', err); process.exit(1); });

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

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