Skip to main content
Glama
any4ai
by any4ai
e2e.mcp.test.ts7.72 kB
import express, { Request, Response } from 'express'; import cors from 'cors'; import helmet from 'helmet'; import { AnyCrawlMCPServer } from '../mcp-server'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; describe('MCP e2e over Streamable HTTP', () => { const API_KEY = 'test-key'; test('initialize session, list tools, and call scrape', async () => { process.env.ANYCRAWL_API_KEY = API_KEY; const consoleSpy = jest.spyOn(console, 'error'); const app = express(); app.use(helmet()); app.use(express.json({ limit: '1mb' })); app.use(cors({ origin: '*', exposedHeaders: ['Mcp-Session-Id'], allowedHeaders: ['Content-Type', 'mcp-session-id'] })); const server = new AnyCrawlMCPServer(API_KEY); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => 'test-session', enableJsonResponse: true }); // Inline minimal bridge (transport as any).onmessage = async (message: any) => { const id = message?.id; if (message?.method === 'initialize') { await (transport as any).send({ jsonrpc: '2.0', id, result: { capabilities: {}, serverInfo: { name: 'AnyCrawl MCP Server', version: '1.0.0' } } }); return; } if (message?.method === 'tools/list') { const tools = (server as any).getToolDefinitions().map((t: any) => ({ name: t.name, description: t.description })); await (transport as any).send({ jsonrpc: '2.0', id, result: { tools } }); return; } if (message?.method === 'tools/call') { const params = message.params || {}; const result = await (server as any).handleToolCall({ name: params.name, arguments: params.arguments || {} }); await (transport as any).send({ jsonrpc: '2.0', id, result }); return; } await (transport as any).send({ jsonrpc: '2.0', id, error: { code: -32601, message: 'Method not found' } }); }; // Mock client return for scrape // Replace SDK client with manual mock const mockedClient = { scrape: jest.fn().mockResolvedValue({ url: 'https://example.com', status: 'completed', markdown: '# ok' }), crawl: jest.fn(), getCrawlStatus: jest.fn(), getCrawlResults: jest.fn(), cancelCrawl: jest.fn(), search: jest.fn(), } as any; (server as any)['client'] = mockedClient; // Defer HTTP server binding before network calls app.post('/mcp', async (req: Request, res: Response) => { await transport.handleRequest(req, res, req.body); }); const httpServer = await new Promise<import('http').Server>((resolve) => { const s = app.listen(0, '127.0.0.1', () => resolve(s)); }); try { const address = httpServer.address(); if (!address || typeof address === 'string') throw new Error('invalid address'); const baseUrl = `http://127.0.0.1:${address.port}`; // Initialize MCP session const initResp = await fetch(`${baseUrl}/mcp`, { method: 'POST', headers: { Accept: 'application/json, text/event-stream', 'Content-Type': 'application/json', 'Mcp-Protocol-Version': '2025-03-26', } as any, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: '2025-03-26', clientInfo: { name: 'jest', version: '0.0.0' }, capabilities: {} }, }), }); expect(initResp.status).toBe(200); const sessionId2 = initResp.headers.get('mcp-session-id'); expect(sessionId2).toBe('test-session'); // Unknown method should return -32601 const unknownResp = await fetch(`${baseUrl}/mcp`, { method: 'POST', headers: { Accept: 'application/json, text/event-stream', 'Content-Type': 'application/json', 'Mcp-Protocol-Version': '2025-03-26', 'Mcp-Session-Id': sessionId2!, } as any, body: JSON.stringify({ jsonrpc: '2.0', id: 999, method: 'unknown/method', params: {} }), }); const unknownJson = await unknownResp.json(); expect([-32601, -32000]).toContain(unknownJson.error.code); // tools/list contains expected tools const list2 = await fetch(`${baseUrl}/mcp`, { method: 'POST', headers: { Accept: 'application/json, text/event-stream', 'Content-Type': 'application/json', 'Mcp-Protocol-Version': '2025-03-26', 'Mcp-Session-Id': sessionId2!, } as any, body: JSON.stringify({ jsonrpc: '2.0', id: 4, method: 'tools/list', params: {} }), }); const list2Json = await list2.json(); const names = (list2Json.result.tools as Array<{ name: string }>).map(t => t.name); expect(names).toEqual(expect.arrayContaining(['anycrawl_scrape', 'anycrawl_crawl', 'anycrawl_search'])); // List tools const listResp = await fetch(`${baseUrl}/mcp`, { method: 'POST', headers: { Accept: 'application/json, text/event-stream', 'Content-Type': 'application/json', 'Mcp-Protocol-Version': '2025-03-26', 'Mcp-Session-Id': sessionId2!, } as any, body: JSON.stringify({ jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} }), }); const listJson = await listResp.json(); expect(listJson.result).toBeTruthy(); const toolNames = (listJson.result.tools as Array<{ name: string }>).map((t) => t.name); expect(toolNames).toEqual(expect.arrayContaining(['anycrawl_scrape', 'anycrawl_crawl', 'anycrawl_search'])); // Call scrape tool const callResp = await fetch(`${baseUrl}/mcp`, { method: 'POST', headers: { Accept: 'application/json, text/event-stream', 'Content-Type': 'application/json', 'Mcp-Protocol-Version': '2025-03-26', 'Mcp-Session-Id': sessionId2!, } as any, body: JSON.stringify({ jsonrpc: '2.0', id: 3, method: 'tools/call', params: { name: 'anycrawl_scrape', arguments: { url: 'https://example.com', engine: 'cheerio' } }, }), }); const callJson = await callResp.json(); expect(callJson.result).toBeTruthy(); const text = callJson.result.content?.[0]?.text as string; const parsed = JSON.parse(text); expect(parsed.url).toBe('https://example.com'); expect(parsed.status).toBe('completed'); // Verify server logged important events (INFO level) expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Starting scrape for URL:')); } finally { await new Promise<void>((resolve) => httpServer.close(() => resolve())) } }, 20000); });

Latest Blog Posts

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

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