11import { expect , test } from '@playwright/test' ;
2- import { waitForRequest } from '@sentry-internal/test-utils' ;
2+ import { getSpanOp , waitForStreamedSpan } from '@sentry-internal/test-utils' ;
33
44const 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-
196test . describe . configure ( { mode : 'serial' } ) ;
207
218test ( '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,60 @@ 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 ;
8159
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: ʕっ•ᴥ•ʔっ' ) ;
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 ) . toMatchObject ( {
64+ 'sentry.origin' : { value : 'auto.http.cloudflare' , type : 'string' } ,
65+ 'sentry.op' : { value : 'http.server' , type : 'string' } ,
66+ 'sentry.segment.name.source' : { value : 'url' , type : 'string' } ,
67+ 'http.request.method' : { value : 'POST' , type : 'string' } ,
68+ 'url.path' : { value : '/mcp' , type : 'string' } ,
69+ 'url.full' : { value : url , type : 'string' } ,
70+ 'url.port' : { value : '38787' , type : 'string' } ,
71+ 'url.scheme' : { value : 'http:' , type : 'string' } ,
72+ 'server.address' : { value : 'localhost' , type : 'string' } ,
73+ 'http.request.body.size' : { value : 341 , type : 'integer' } ,
74+ 'user_agent.original' : { value : 'node' , type : 'string' } ,
75+ 'http.request.header.content_type' : { value : 'application/json' , type : 'string' } ,
76+ 'network.protocol.name' : { value : 'http' , type : 'string' } ,
77+ 'network.protocol.version' : { value : '1.1' , type : 'string' } ,
78+ 'http.response.status_code' : { value : 200 , type : 'integer' } ,
79+ 'mcp.server.extra' : { value : ' /|\ ^._.^ /|\ ' , type : 'string' } ,
80+ } ) ;
81+
82+ expect ( mcpSpan . trace_id ) . toBe ( requestSpan . trace_id ) ;
83+ expect ( mcpSpan . parent_span_id ) . toBe ( requestSpan . span_id ) ;
84+ expect ( mcpSpan . span_id ) . not . toBe ( requestSpan . span_id ) ;
85+ expect ( mcpSpan . status ) . toBe ( 'ok' ) ;
86+ expect ( mcpSpan . attributes ) . toMatchObject ( {
87+ 'sentry.op' : { value : 'mcp.server' , type : 'string' } ,
88+ 'sentry.origin' : { value : 'auto.function.mcp_server' , type : 'string' } ,
89+ 'mcp.transport' : { value : 'PerRequestHTTPServerTransport' , type : 'string' } ,
90+ 'network.transport' : { value : 'tcp' , type : 'string' } ,
91+ 'mcp.protocol.version' : { value : '2026-07-28' , type : 'string' } ,
92+ 'mcp.client.name' : { value : 'cloudflare-modern-client' , type : 'string' } ,
93+ 'mcp.client.version' : { value : '2.0.0' , type : 'string' } ,
94+ 'mcp.server.name' : { value : 'cloudflare-mcp' , type : 'string' } ,
95+ 'mcp.server.version' : { value : '2.0.0' , type : 'string' } ,
96+ 'mcp.method.name' : { value : 'tools/call' , type : 'string' } ,
97+ 'mcp.request.id' : { value : 'modern-tool-call' , type : 'string' } ,
98+ 'mcp.tool.name' : { value : 'my-tool' , type : 'string' } ,
99+ 'mcp.request.argument.message' : { value : '"ʕっ•ᴥ•ʔっ"' , type : 'string' } ,
100+ 'mcp.tool.result.content_count' : { value : 1 , type : 'integer' } ,
101+ 'mcp.tool.result.content' : { value : 'Tool my-tool: ʕっ•ᴥ•ʔっ' , type : 'string' } ,
102+ } ) ;
121103} ) ;
122104
123105test ( 'keeps sending spans for legacy-compatible MCP tool calls' , async ( { baseURL } ) => {
124106 const url = `${ baseURL } /mcp?protocol=legacy` ;
125- const mcpWaiter = waitForRequest ( APP_NAME , eventData => {
126- const event = getTransaction ( eventData ) ;
107+ const mcpSpanPromise = waitForStreamedSpan ( APP_NAME , span => {
127108 return (
128- event ?. transaction === 'tools/call my-tool' &&
129- event . contexts ?. trace ?. data ?. [ 'mcp.request.argument.message' ] === '"legacy protocol request"'
109+ span . name === 'tools/call my-tool' &&
110+ span . attributes [ 'mcp.request.argument.message' ] ?. value === '"legacy protocol request"'
130111 ) ;
131112 } ) ;
132113
@@ -151,15 +132,16 @@ test('keeps sending spans for legacy-compatible MCP tool calls', async ({ baseUR
151132
152133 expect ( response . status ) . toBe ( 200 ) ;
153134
154- const mcpEvent = requireTransaction ( await mcpWaiter ) ;
155- const trace = mcpEvent . contexts ?. trace ;
135+ const mcpSpan = await mcpSpanPromise ;
156136
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' ) ;
137+ expect ( getSpanOp ( mcpSpan ) ) . toBe ( 'mcp.server' ) ;
138+ expect ( mcpSpan . status ) . toBe ( 'ok' ) ;
139+ expect ( mcpSpan . attributes ) . toMatchObject ( {
140+ 'mcp.transport' : { value : 'WebStandardStreamableHTTPServerTransport' , type : 'string' } ,
141+ 'mcp.method.name' : { value : 'tools/call' , type : 'string' } ,
142+ 'mcp.request.id' : { value : 'legacy-tool-call' , type : 'string' } ,
143+ 'mcp.tool.name' : { value : 'my-tool' , type : 'string' } ,
144+ 'mcp.tool.result.content' : { value : 'Tool my-tool: legacy protocol request' , type : 'string' } ,
145+ } ) ;
146+ expect ( mcpSpan . attributes [ 'mcp.protocol.version' ] ) . toBeUndefined ( ) ;
165147} ) ;
0 commit comments