From ec269794f5bb4641371e82abd1136274882a3a39 Mon Sep 17 00:00:00 2001 From: JPeer264 Date: Tue, 1 Sep 2026 12:21:15 +0200 Subject: [PATCH] test(e2e): Port cloudflare-mcp to span streaming Co-Authored-By: Claude Fable 5 --- .../cloudflare-mcp/src/index.ts | 1 - .../cloudflare-mcp/tests/index.test.ts | 146 ++++++++---------- 2 files changed, 64 insertions(+), 83 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-mcp/src/index.ts b/dev-packages/e2e-tests/test-applications/cloudflare-mcp/src/index.ts index c39410af06ae..1dac0ff759e5 100644 --- a/dev-packages/e2e-tests/test-applications/cloudflare-mcp/src/index.ts +++ b/dev-packages/e2e-tests/test-applications/cloudflare-mcp/src/index.ts @@ -63,7 +63,6 @@ const mcpHandler = createMcpHandler(createServer, { export default Sentry.withSentry( (env: Env) => ({ - traceLifecycle: 'static', dsn: env.E2E_TEST_DSN, environment: 'qa', // dynamic sampling bias to keep transactions tunnel: `http://localhost:3031/`, // proxy server diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-mcp/tests/index.test.ts b/dev-packages/e2e-tests/test-applications/cloudflare-mcp/tests/index.test.ts index 9c9353833313..288030ddda25 100644 --- a/dev-packages/e2e-tests/test-applications/cloudflare-mcp/tests/index.test.ts +++ b/dev-packages/e2e-tests/test-applications/cloudflare-mcp/tests/index.test.ts @@ -1,35 +1,17 @@ import { expect, test } from '@playwright/test'; -import { waitForRequest } from '@sentry-internal/test-utils'; +import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; const APP_NAME = 'cloudflare-mcp'; -function getTransaction(eventData: Awaited>) { - const event = eventData.envelope[1][0][1]; - return typeof event !== 'string' && 'transaction' in event ? event : undefined; -} - -function requireTransaction(eventData: Awaited>) { - const event = getTransaction(eventData); - if (!event) { - throw new Error('Expected a transaction event'); - } - return event; -} - test.describe.configure({ mode: 'serial' }); test('sends spans for MCP 2026-07-28 tool calls', async ({ baseURL }) => { const url = `${baseURL}/mcp?protocol=modern`; - const requestWaiter = waitForRequest(APP_NAME, eventData => { - const event = getTransaction(eventData); - return event?.transaction === 'POST /mcp' && event.contexts?.trace?.data?.['url.full'] === url; + const requestSpanPromise = waitForStreamedSpan(APP_NAME, span => { + return getSpanOp(span) === 'http.server' && span.is_segment && span.attributes['url.full']?.value === url; }); - const mcpWaiter = waitForRequest(APP_NAME, eventData => { - const event = getTransaction(eventData); - return ( - event?.transaction === 'tools/call my-tool' && - event.contexts?.trace?.data?.['mcp.protocol.version'] === '2026-07-28' - ); + const mcpSpanPromise = waitForStreamedSpan(APP_NAME, span => { + return span.name === 'tools/call my-tool' && span.attributes['mcp.protocol.version']?.value === '2026-07-28'; }); const response = await fetch(url, { @@ -72,61 +54,60 @@ test('sends spans for MCP 2026-07-28 tool calls', async ({ baseURL }) => { }, }); - const requestData = await requestWaiter; - const mcpData = await mcpWaiter; - const requestEvent = requireTransaction(requestData); - const mcpEvent = requireTransaction(mcpData); - const requestTrace = requestEvent.contexts?.trace; - const mcpTrace = mcpEvent.contexts?.trace; + const requestSpan = await requestSpanPromise; + const mcpSpan = await mcpSpanPromise; - expect(requestTrace?.op).toBe('http.server'); - expect(requestTrace?.origin).toBe('auto.http.cloudflare'); - expect(requestTrace?.status).toBe('ok'); - expect(requestTrace?.data?.['sentry.origin']).toBe('auto.http.cloudflare'); - expect(requestTrace?.data?.['sentry.op']).toBe('http.server'); - expect(requestTrace?.data?.['sentry.segment.name.source']).toBe('url'); - expect(requestTrace?.data?.['http.request.method']).toBe('POST'); - expect(requestTrace?.data?.['url.path']).toBe('/mcp'); - expect(requestTrace?.data?.['url.full']).toBe(url); - expect(requestTrace?.data?.['url.port']).toBe('38787'); - expect(requestTrace?.data?.['url.scheme']).toBe('http:'); - expect(requestTrace?.data?.['server.address']).toBe('localhost'); - expect(requestTrace?.data?.['http.request.body.size']).toBe(341); - expect(requestTrace?.data?.['user_agent.original']).toBe('node'); - expect(requestTrace?.data?.['http.request.header.content_type']).toBe('application/json'); - expect(requestTrace?.data?.['network.protocol.name']).toBe('http'); - expect(requestTrace?.data?.['network.protocol.version']).toBe('1.1'); - expect(requestTrace?.data?.['http.response.status_code']).toBe(200); - expect(requestTrace?.data?.['mcp.server.extra']).toBe(' /|\ ^._.^ /|\ '); - expect(mcpTrace?.trace_id).toBe(requestTrace?.trace_id); - expect(mcpTrace?.trace_id).toBe((mcpData.envelope[0].trace as { trace_id: string }).trace_id); - expect(mcpTrace?.parent_span_id).toBe(requestTrace?.span_id); - expect(requestData.envelope[0].event_id).not.toBe(mcpData.envelope[0].event_id); - expect(mcpTrace?.op).toBe('mcp.server'); - expect(mcpTrace?.origin).toBe('auto.function.mcp_server'); - expect(mcpTrace?.status).toBe('ok'); - expect(mcpTrace?.data?.['mcp.transport']).toBe('PerRequestHTTPServerTransport'); - expect(mcpTrace?.data?.['network.transport']).toBe('tcp'); - expect(mcpTrace?.data?.['mcp.protocol.version']).toBe('2026-07-28'); - expect(mcpTrace?.data?.['mcp.client.name']).toBe('cloudflare-modern-client'); - expect(mcpTrace?.data?.['mcp.client.version']).toBe('2.0.0'); - expect(mcpTrace?.data?.['mcp.server.name']).toBe('cloudflare-mcp'); - expect(mcpTrace?.data?.['mcp.server.version']).toBe('2.0.0'); - expect(mcpTrace?.data?.['mcp.method.name']).toBe('tools/call'); - expect(mcpTrace?.data?.['mcp.request.id']).toBe('modern-tool-call'); - expect(mcpTrace?.data?.['mcp.tool.name']).toBe('my-tool'); - expect(mcpTrace?.data?.['mcp.request.argument.message']).toBe('"ʕっ•ᴥ•ʔっ"'); - expect(mcpTrace?.data?.['mcp.tool.result.content_count']).toBe(1); - expect(mcpTrace?.data?.['mcp.tool.result.content']).toBe('Tool my-tool: ʕっ•ᴥ•ʔっ'); + // With span streaming, URL-sourced `http.server` spans are named by method only. + expect(requestSpan.name).toBe('POST'); + expect(requestSpan.status).toBe('ok'); + expect(requestSpan.attributes).toMatchObject({ + 'sentry.origin': { value: 'auto.http.cloudflare', type: 'string' }, + 'sentry.op': { value: 'http.server', type: 'string' }, + 'sentry.segment.name.source': { value: 'url', type: 'string' }, + 'http.request.method': { value: 'POST', type: 'string' }, + 'url.path': { value: '/mcp', type: 'string' }, + 'url.full': { value: url, type: 'string' }, + 'url.port': { value: '38787', type: 'string' }, + 'url.scheme': { value: 'http:', type: 'string' }, + 'server.address': { value: 'localhost', type: 'string' }, + 'http.request.body.size': { value: 341, type: 'integer' }, + 'user_agent.original': { value: 'node', type: 'string' }, + 'http.request.header.content_type': { value: 'application/json', type: 'string' }, + 'network.protocol.name': { value: 'http', type: 'string' }, + 'network.protocol.version': { value: '1.1', type: 'string' }, + 'http.response.status_code': { value: 200, type: 'integer' }, + 'mcp.server.extra': { value: ' /|\ ^._.^ /|\ ', type: 'string' }, + }); + + expect(mcpSpan.trace_id).toBe(requestSpan.trace_id); + expect(mcpSpan.parent_span_id).toBe(requestSpan.span_id); + expect(mcpSpan.span_id).not.toBe(requestSpan.span_id); + expect(mcpSpan.status).toBe('ok'); + expect(mcpSpan.attributes).toMatchObject({ + 'sentry.op': { value: 'mcp.server', type: 'string' }, + 'sentry.origin': { value: 'auto.function.mcp_server', type: 'string' }, + 'mcp.transport': { value: 'PerRequestHTTPServerTransport', type: 'string' }, + 'network.transport': { value: 'tcp', type: 'string' }, + 'mcp.protocol.version': { value: '2026-07-28', type: 'string' }, + 'mcp.client.name': { value: 'cloudflare-modern-client', type: 'string' }, + 'mcp.client.version': { value: '2.0.0', type: 'string' }, + 'mcp.server.name': { value: 'cloudflare-mcp', type: 'string' }, + 'mcp.server.version': { value: '2.0.0', type: 'string' }, + 'mcp.method.name': { value: 'tools/call', type: 'string' }, + 'mcp.request.id': { value: 'modern-tool-call', type: 'string' }, + 'mcp.tool.name': { value: 'my-tool', type: 'string' }, + 'mcp.request.argument.message': { value: '"ʕっ•ᴥ•ʔっ"', type: 'string' }, + 'mcp.tool.result.content_count': { value: 1, type: 'integer' }, + 'mcp.tool.result.content': { value: 'Tool my-tool: ʕっ•ᴥ•ʔっ', type: 'string' }, + }); }); test('keeps sending spans for legacy-compatible MCP tool calls', async ({ baseURL }) => { const url = `${baseURL}/mcp?protocol=legacy`; - const mcpWaiter = waitForRequest(APP_NAME, eventData => { - const event = getTransaction(eventData); + const mcpSpanPromise = waitForStreamedSpan(APP_NAME, span => { return ( - event?.transaction === 'tools/call my-tool' && - event.contexts?.trace?.data?.['mcp.request.argument.message'] === '"legacy protocol request"' + span.name === 'tools/call my-tool' && + span.attributes['mcp.request.argument.message']?.value === '"legacy protocol request"' ); }); @@ -151,15 +132,16 @@ test('keeps sending spans for legacy-compatible MCP tool calls', async ({ baseUR expect(response.status).toBe(200); - const mcpEvent = requireTransaction(await mcpWaiter); - const trace = mcpEvent.contexts?.trace; + const mcpSpan = await mcpSpanPromise; - expect(trace?.op).toBe('mcp.server'); - expect(trace?.status).toBe('ok'); - expect(trace?.data?.['mcp.transport']).toBe('WebStandardStreamableHTTPServerTransport'); - expect(trace?.data?.['mcp.method.name']).toBe('tools/call'); - expect(trace?.data?.['mcp.request.id']).toBe('legacy-tool-call'); - expect(trace?.data?.['mcp.tool.name']).toBe('my-tool'); - expect(trace?.data?.['mcp.protocol.version']).toBeUndefined(); - expect(trace?.data?.['mcp.tool.result.content']).toBe('Tool my-tool: legacy protocol request'); + expect(getSpanOp(mcpSpan)).toBe('mcp.server'); + expect(mcpSpan.status).toBe('ok'); + expect(mcpSpan.attributes).toMatchObject({ + 'mcp.transport': { value: 'WebStandardStreamableHTTPServerTransport', type: 'string' }, + 'mcp.method.name': { value: 'tools/call', type: 'string' }, + 'mcp.request.id': { value: 'legacy-tool-call', type: 'string' }, + 'mcp.tool.name': { value: 'my-tool', type: 'string' }, + 'mcp.tool.result.content': { value: 'Tool my-tool: legacy protocol request', type: 'string' }, + }); + expect(mcpSpan.attributes['mcp.protocol.version']).toBeUndefined(); });