Skip to main content
Glama

Readwise MCP Server

by IAmAlexander
video-features.test.ts10.7 kB
import request from 'supertest'; import express from 'express'; import cors from 'cors'; // Mock Express app for testing const app = express(); app.use(cors()); app.use(express.json()); // Mock videos endpoint app.get('/videos', (req, res) => { const pageCursor = req.query.pageCursor; const limit = parseInt(req.query.limit as string) || 10; const mockVideos = [ { id: 'video1', title: 'Introduction to TypeScript', url: 'https://www.youtube.com/watch?v=abc123', created_at: '2023-01-15T12:00:00Z', updated_at: '2023-01-15T12:00:00Z', author: 'Tech Channel', tags: ['programming', 'typescript'] }, { id: 'video2', title: 'AI Safety Explained', url: 'https://www.youtube.com/watch?v=def456', created_at: '2023-02-20T14:30:00Z', updated_at: '2023-02-20T14:30:00Z', author: 'AI Research', tags: ['ai', 'safety'] }, { id: 'video3', title: 'Web Development in 2023', url: 'https://youtu.be/ghi789', created_at: '2023-03-10T09:15:00Z', updated_at: '2023-03-10T09:15:00Z', author: 'Dev Channel', tags: ['web', 'development'] } ]; const nextPageCursor = pageCursor ? null : 'next_page_token'; res.status(200).json({ count: mockVideos.length, results: mockVideos.slice(0, limit), nextPageCursor }); }); // Mock video details endpoint app.get('/video/:document_id', (req, res) => { const documentId = req.params.document_id; if (documentId !== 'video1' && documentId !== 'video2' && documentId !== 'video3') { return res.status(404).json({ error: 'Video not found' }); } const mockTranscript = [ { timestamp: '0:00', text: 'Hello and welcome to this video.' }, { timestamp: '0:15', text: 'Today we\'ll be discussing an important topic.' }, { timestamp: '0:30', text: 'Let\'s dive right in.' }, { timestamp: '1:45', text: 'This is a key point to remember.' }, { timestamp: '2:30', text: 'Now let\'s look at some examples.' }, { timestamp: '5:15', text: 'To summarize what we\'ve learned.' }, { timestamp: '6:00', text: 'Thanks for watching!' } ]; const videoDetails = { id: documentId, title: documentId === 'video1' ? 'Introduction to TypeScript' : documentId === 'video2' ? 'AI Safety Explained' : 'Web Development in 2023', url: documentId === 'video1' ? 'https://www.youtube.com/watch?v=abc123' : documentId === 'video2' ? 'https://www.youtube.com/watch?v=def456' : 'https://youtu.be/ghi789', created_at: '2023-01-15T12:00:00Z', updated_at: '2023-01-15T12:00:00Z', author: documentId === 'video1' ? 'Tech Channel' : documentId === 'video2' ? 'AI Research' : 'Dev Channel', tags: documentId === 'video1' ? ['programming', 'typescript'] : documentId === 'video2' ? ['ai', 'safety'] : ['web', 'development'], transcript: mockTranscript }; res.status(200).json(videoDetails); }); // Mock video highlights endpoint app.get('/video/:document_id/highlights', (req, res) => { const documentId = req.params.document_id; if (documentId !== 'video1' && documentId !== 'video2' && documentId !== 'video3') { return res.status(404).json({ error: 'Video not found' }); } const mockHighlights = [ { id: 'highlight1', text: 'This is a key point to remember.', note: 'Important concept', timestamp: '1:45', created_at: '2023-01-16T10:30:00Z', updated_at: '2023-01-16T10:30:00Z' }, { id: 'highlight2', text: 'To summarize what we\'ve learned.', note: null, timestamp: '5:15', created_at: '2023-01-16T10:35:00Z', updated_at: '2023-01-16T10:35:00Z' } ]; res.status(200).json({ count: mockHighlights.length, results: mockHighlights }); }); // Mock create video highlight endpoint app.post('/video/:document_id/highlight', (req, res) => { const documentId = req.params.document_id; if (documentId !== 'video1' && documentId !== 'video2' && documentId !== 'video3') { return res.status(404).json({ error: 'Video not found' }); } const { text, timestamp, note } = req.body; if (!text) { return res.status(400).json({ error: 'Highlight text is required' }); } const newHighlight = { id: 'new_highlight_id', text, timestamp, note, created_at: new Date().toISOString(), updated_at: new Date().toISOString() }; res.status(201).json(newHighlight); }); // Mock video position endpoints app.get('/video/:document_id/position', (req, res) => { const documentId = req.params.document_id; if (documentId !== 'video1' && documentId !== 'video2' && documentId !== 'video3') { return res.status(404).json({ error: 'Video not found' }); } const mockPosition = { document_id: documentId, position: 125.5, percentage: 35, last_updated: new Date().toISOString() }; res.status(200).json(mockPosition); }); app.post('/video/:document_id/position', (req, res) => { const documentId = req.params.document_id; if (documentId !== 'video1' && documentId !== 'video2' && documentId !== 'video3') { return res.status(404).json({ error: 'Video not found' }); } const { position, duration } = req.body; if (position === undefined) { return res.status(400).json({ error: 'Playback position is required' }); } const progressPercentage = duration ? Math.min(100, Math.round((position / duration) * 100)) : null; const updatedPosition = { document_id: documentId, position, percentage: progressPercentage || 0, last_updated: new Date().toISOString() }; res.status(200).json(updatedPosition); }); describe('Video Features', () => { describe('List Videos', () => { it('should return a list of videos', async () => { const response = await request(app).get('/videos'); expect(response.status).toBe(200); expect(response.body).toHaveProperty('count'); expect(response.body).toHaveProperty('results'); expect(response.body).toHaveProperty('nextPageCursor'); expect(Array.isArray(response.body.results)).toBe(true); expect(response.body.results.length).toBeGreaterThan(0); const video = response.body.results[0]; expect(video).toHaveProperty('id'); expect(video).toHaveProperty('title'); expect(video).toHaveProperty('url'); expect(video.url).toMatch(/youtube\.com|youtu\.be|vimeo\.com|dailymotion\.com|twitch\.tv/); }); it('should respect the limit parameter', async () => { const limit = 2; const response = await request(app).get(`/videos?limit=${limit}`); expect(response.status).toBe(200); expect(response.body.results.length).toBeLessThanOrEqual(limit); }); }); describe('Get Video Details', () => { it('should return video details with transcript', async () => { const response = await request(app).get('/video/video1'); expect(response.status).toBe(200); expect(response.body).toHaveProperty('id', 'video1'); expect(response.body).toHaveProperty('title'); expect(response.body).toHaveProperty('url'); expect(response.body).toHaveProperty('transcript'); expect(Array.isArray(response.body.transcript)).toBe(true); const transcriptItem = response.body.transcript[0]; expect(transcriptItem).toHaveProperty('timestamp'); expect(transcriptItem).toHaveProperty('text'); }); it('should return 404 for non-existent video', async () => { const response = await request(app).get('/video/nonexistent'); expect(response.status).toBe(404); }); }); describe('Video Highlights', () => { it('should return highlights for a video', async () => { const response = await request(app).get('/video/video1/highlights'); expect(response.status).toBe(200); expect(response.body).toHaveProperty('count'); expect(response.body).toHaveProperty('results'); expect(Array.isArray(response.body.results)).toBe(true); const highlight = response.body.results[0]; expect(highlight).toHaveProperty('id'); expect(highlight).toHaveProperty('text'); expect(highlight).toHaveProperty('timestamp'); }); it('should create a new highlight with timestamp', async () => { const highlightData = { text: 'This is a test highlight', timestamp: '2:30', note: 'Test note' }; const response = await request(app) .post('/video/video1/highlight') .send(highlightData); expect(response.status).toBe(201); expect(response.body).toHaveProperty('id'); expect(response.body).toHaveProperty('text', highlightData.text); expect(response.body).toHaveProperty('timestamp', highlightData.timestamp); expect(response.body).toHaveProperty('note', highlightData.note); }); it('should require highlight text', async () => { const highlightData = { timestamp: '2:30' }; const response = await request(app) .post('/video/video1/highlight') .send(highlightData); expect(response.status).toBe(400); }); }); describe('Video Playback Position', () => { it('should return the current playback position', async () => { const response = await request(app).get('/video/video1/position'); expect(response.status).toBe(200); expect(response.body).toHaveProperty('document_id', 'video1'); expect(response.body).toHaveProperty('position'); expect(response.body).toHaveProperty('percentage'); expect(response.body).toHaveProperty('last_updated'); }); it('should update the playback position', async () => { const positionData = { position: 180.5, duration: 360 }; const response = await request(app) .post('/video/video1/position') .send(positionData); expect(response.status).toBe(200); expect(response.body).toHaveProperty('document_id', 'video1'); expect(response.body).toHaveProperty('position', positionData.position); expect(response.body).toHaveProperty('percentage', 50); // 180.5/360 = 50% }); it('should require position parameter', async () => { const positionData = { duration: 360 }; const response = await request(app) .post('/video/video1/position') .send(positionData); expect(response.status).toBe(400); }); }); });

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/IAmAlexander/readwise-mcp'

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