Skip to content

Commit 846bd2d

Browse files
JPeer264claude
andcommitted
test(e2e): Port cloudflare-mcp to span streaming
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent c90484e commit 846bd2d

2 files changed

Lines changed: 58 additions & 83 deletions

File tree

‎dev-packages/e2e-tests/test-applications/cloudflare-mcp/src/index.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ const mcpHandler = createMcpHandler(createServer, {
6363

6464
export default Sentry.withSentry(
6565
(env: Env) => ({
66-
traceLifecycle: 'static',
6766
dsn: env.E2E_TEST_DSN,
6867
environment: 'qa', // dynamic sampling bias to keep transactions
6968
tunnel: `http://localhost:3031/`, // proxy server
Lines changed: 58 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,17 @@
11
import { expect, test } from '@playwright/test';
2-
import { waitForRequest } from '@sentry-internal/test-utils';
2+
import { getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils';
33

44
const APP_NAME = 'cloudflare-mcp';
55

6-
function getTransaction(eventData: Awaited<ReturnType<typeof waitForRequest>>) {
7-
const event = eventData.envelope[1][0][1];
8-
return typeof event !== 'string' && 'transaction' in event ? event : undefined;
9-
}
10-
11-
function requireTransaction(eventData: Awaited<ReturnType<typeof waitForRequest>>) {
12-
const event = getTransaction(eventData);
13-
if (!event) {
14-
throw new Error('Expected a transaction event');
15-
}
16-
return event;
17-
}
18-
196
test.describe.configure({ mode: 'serial' });
207

218
test('sends spans for MCP 2026-07-28 tool calls', async ({ baseURL }) => {
229
const url = `${baseURL}/mcp?protocol=modern`;
23-
const requestWaiter = waitForRequest(APP_NAME, eventData => {
24-
const event = getTransaction(eventData);
25-
return event?.transaction === 'POST /mcp' && event.contexts?.trace?.data?.['url.full'] === url;
10+
const requestSpanPromise = waitForStreamedSpan(APP_NAME, span => {
11+
return getSpanOp(span) === 'http.server' && span.is_segment && span.attributes['url.full']?.value === url;
2612
});
27-
const mcpWaiter = waitForRequest(APP_NAME, eventData => {
28-
const event = getTransaction(eventData);
29-
return (
30-
event?.transaction === 'tools/call my-tool' &&
31-
event.contexts?.trace?.data?.['mcp.protocol.version'] === '2026-07-28'
32-
);
13+
const mcpSpanPromise = waitForStreamedSpan(APP_NAME, span => {
14+
return span.name === 'tools/call my-tool' && span.attributes['mcp.protocol.version']?.value === '2026-07-28';
3315
});
3416

3517
const response = await fetch(url, {
@@ -72,61 +54,56 @@ test('sends spans for MCP 2026-07-28 tool calls', async ({ baseURL }) => {
7254
},
7355
});
7456

75-
const requestData = await requestWaiter;
76-
const mcpData = await mcpWaiter;
77-
const requestEvent = requireTransaction(requestData);
78-
const mcpEvent = requireTransaction(mcpData);
79-
const requestTrace = requestEvent.contexts?.trace;
80-
const mcpTrace = mcpEvent.contexts?.trace;
57+
const requestSpan = await requestSpanPromise;
58+
const mcpSpan = await mcpSpanPromise;
59+
60+
// With span streaming, URL-sourced `http.server` spans are named by method only.
61+
expect(requestSpan.name).toBe('POST');
62+
expect(requestSpan.status).toBe('ok');
63+
expect(requestSpan.attributes['sentry.origin']?.value).toBe('auto.http.cloudflare');
64+
expect(requestSpan.attributes['sentry.op']?.value).toBe('http.server');
65+
expect(requestSpan.attributes['sentry.segment.name.source']?.value).toBe('url');
66+
expect(requestSpan.attributes['http.request.method']?.value).toBe('POST');
67+
expect(requestSpan.attributes['url.path']?.value).toBe('/mcp');
68+
expect(requestSpan.attributes['url.full']?.value).toBe(url);
69+
expect(requestSpan.attributes['url.port']?.value).toBe('38787');
70+
expect(requestSpan.attributes['url.scheme']?.value).toBe('http:');
71+
expect(requestSpan.attributes['server.address']?.value).toBe('localhost');
72+
expect(requestSpan.attributes['http.request.body.size']?.value).toBe(341);
73+
expect(requestSpan.attributes['user_agent.original']?.value).toBe('node');
74+
expect(requestSpan.attributes['http.request.header.content_type']?.value).toBe('application/json');
75+
expect(requestSpan.attributes['network.protocol.name']?.value).toBe('http');
76+
expect(requestSpan.attributes['network.protocol.version']?.value).toBe('1.1');
77+
expect(requestSpan.attributes['http.response.status_code']?.value).toBe(200);
78+
expect(requestSpan.attributes['mcp.server.extra']?.value).toBe(' /|\ ^._.^ /|\ ');
8179

82-
expect(requestTrace?.op).toBe('http.server');
83-
expect(requestTrace?.origin).toBe('auto.http.cloudflare');
84-
expect(requestTrace?.status).toBe('ok');
85-
expect(requestTrace?.data?.['sentry.origin']).toBe('auto.http.cloudflare');
86-
expect(requestTrace?.data?.['sentry.op']).toBe('http.server');
87-
expect(requestTrace?.data?.['sentry.segment.name.source']).toBe('url');
88-
expect(requestTrace?.data?.['http.request.method']).toBe('POST');
89-
expect(requestTrace?.data?.['url.path']).toBe('/mcp');
90-
expect(requestTrace?.data?.['url.full']).toBe(url);
91-
expect(requestTrace?.data?.['url.port']).toBe('38787');
92-
expect(requestTrace?.data?.['url.scheme']).toBe('http:');
93-
expect(requestTrace?.data?.['server.address']).toBe('localhost');
94-
expect(requestTrace?.data?.['http.request.body.size']).toBe(341);
95-
expect(requestTrace?.data?.['user_agent.original']).toBe('node');
96-
expect(requestTrace?.data?.['http.request.header.content_type']).toBe('application/json');
97-
expect(requestTrace?.data?.['network.protocol.name']).toBe('http');
98-
expect(requestTrace?.data?.['network.protocol.version']).toBe('1.1');
99-
expect(requestTrace?.data?.['http.response.status_code']).toBe(200);
100-
expect(requestTrace?.data?.['mcp.server.extra']).toBe(' /|\ ^._.^ /|\ ');
101-
expect(mcpTrace?.trace_id).toBe(requestTrace?.trace_id);
102-
expect(mcpTrace?.trace_id).toBe((mcpData.envelope[0].trace as { trace_id: string }).trace_id);
103-
expect(mcpTrace?.parent_span_id).toBe(requestTrace?.span_id);
104-
expect(requestData.envelope[0].event_id).not.toBe(mcpData.envelope[0].event_id);
105-
expect(mcpTrace?.op).toBe('mcp.server');
106-
expect(mcpTrace?.origin).toBe('auto.function.mcp_server');
107-
expect(mcpTrace?.status).toBe('ok');
108-
expect(mcpTrace?.data?.['mcp.transport']).toBe('PerRequestHTTPServerTransport');
109-
expect(mcpTrace?.data?.['network.transport']).toBe('tcp');
110-
expect(mcpTrace?.data?.['mcp.protocol.version']).toBe('2026-07-28');
111-
expect(mcpTrace?.data?.['mcp.client.name']).toBe('cloudflare-modern-client');
112-
expect(mcpTrace?.data?.['mcp.client.version']).toBe('2.0.0');
113-
expect(mcpTrace?.data?.['mcp.server.name']).toBe('cloudflare-mcp');
114-
expect(mcpTrace?.data?.['mcp.server.version']).toBe('2.0.0');
115-
expect(mcpTrace?.data?.['mcp.method.name']).toBe('tools/call');
116-
expect(mcpTrace?.data?.['mcp.request.id']).toBe('modern-tool-call');
117-
expect(mcpTrace?.data?.['mcp.tool.name']).toBe('my-tool');
118-
expect(mcpTrace?.data?.['mcp.request.argument.message']).toBe('"ʕっ•ᴥ•ʔっ"');
119-
expect(mcpTrace?.data?.['mcp.tool.result.content_count']).toBe(1);
120-
expect(mcpTrace?.data?.['mcp.tool.result.content']).toBe('Tool my-tool: ʕっ•ᴥ•ʔっ');
80+
expect(mcpSpan.trace_id).toBe(requestSpan.trace_id);
81+
expect(mcpSpan.parent_span_id).toBe(requestSpan.span_id);
82+
expect(mcpSpan.span_id).not.toBe(requestSpan.span_id);
83+
expect(mcpSpan.status).toBe('ok');
84+
expect(mcpSpan.attributes['sentry.op']?.value).toBe('mcp.server');
85+
expect(mcpSpan.attributes['sentry.origin']?.value).toBe('auto.function.mcp_server');
86+
expect(mcpSpan.attributes['mcp.transport']?.value).toBe('PerRequestHTTPServerTransport');
87+
expect(mcpSpan.attributes['network.transport']?.value).toBe('tcp');
88+
expect(mcpSpan.attributes['mcp.protocol.version']?.value).toBe('2026-07-28');
89+
expect(mcpSpan.attributes['mcp.client.name']?.value).toBe('cloudflare-modern-client');
90+
expect(mcpSpan.attributes['mcp.client.version']?.value).toBe('2.0.0');
91+
expect(mcpSpan.attributes['mcp.server.name']?.value).toBe('cloudflare-mcp');
92+
expect(mcpSpan.attributes['mcp.server.version']?.value).toBe('2.0.0');
93+
expect(mcpSpan.attributes['mcp.method.name']?.value).toBe('tools/call');
94+
expect(mcpSpan.attributes['mcp.request.id']?.value).toBe('modern-tool-call');
95+
expect(mcpSpan.attributes['mcp.tool.name']?.value).toBe('my-tool');
96+
expect(mcpSpan.attributes['mcp.request.argument.message']?.value).toBe('"ʕっ•ᴥ•ʔっ"');
97+
expect(mcpSpan.attributes['mcp.tool.result.content_count']?.value).toBe(1);
98+
expect(mcpSpan.attributes['mcp.tool.result.content']?.value).toBe('Tool my-tool: ʕっ•ᴥ•ʔっ');
12199
});
122100

123101
test('keeps sending spans for legacy-compatible MCP tool calls', async ({ baseURL }) => {
124102
const url = `${baseURL}/mcp?protocol=legacy`;
125-
const mcpWaiter = waitForRequest(APP_NAME, eventData => {
126-
const event = getTransaction(eventData);
103+
const mcpSpanPromise = waitForStreamedSpan(APP_NAME, span => {
127104
return (
128-
event?.transaction === 'tools/call my-tool' &&
129-
event.contexts?.trace?.data?.['mcp.request.argument.message'] === '"legacy protocol request"'
105+
span.name === 'tools/call my-tool' &&
106+
span.attributes['mcp.request.argument.message']?.value === '"legacy protocol request"'
130107
);
131108
});
132109

@@ -151,15 +128,14 @@ test('keeps sending spans for legacy-compatible MCP tool calls', async ({ baseUR
151128

152129
expect(response.status).toBe(200);
153130

154-
const mcpEvent = requireTransaction(await mcpWaiter);
155-
const trace = mcpEvent.contexts?.trace;
131+
const mcpSpan = await mcpSpanPromise;
156132

157-
expect(trace?.op).toBe('mcp.server');
158-
expect(trace?.status).toBe('ok');
159-
expect(trace?.data?.['mcp.transport']).toBe('WebStandardStreamableHTTPServerTransport');
160-
expect(trace?.data?.['mcp.method.name']).toBe('tools/call');
161-
expect(trace?.data?.['mcp.request.id']).toBe('legacy-tool-call');
162-
expect(trace?.data?.['mcp.tool.name']).toBe('my-tool');
163-
expect(trace?.data?.['mcp.protocol.version']).toBeUndefined();
164-
expect(trace?.data?.['mcp.tool.result.content']).toBe('Tool my-tool: legacy protocol request');
133+
expect(getSpanOp(mcpSpan)).toBe('mcp.server');
134+
expect(mcpSpan.status).toBe('ok');
135+
expect(mcpSpan.attributes['mcp.transport']?.value).toBe('WebStandardStreamableHTTPServerTransport');
136+
expect(mcpSpan.attributes['mcp.method.name']?.value).toBe('tools/call');
137+
expect(mcpSpan.attributes['mcp.request.id']?.value).toBe('legacy-tool-call');
138+
expect(mcpSpan.attributes['mcp.tool.name']?.value).toBe('my-tool');
139+
expect(mcpSpan.attributes['mcp.tool.result.content']?.value).toBe('Tool my-tool: legacy protocol request');
140+
expect(mcpSpan.attributes['mcp.protocol.version']).toBeUndefined();
165141
});

0 commit comments

Comments
 (0)