@@ -169,6 +169,190 @@ describe('wrapMcpServerWithSentry', () => {
169
169
expect ( ( ) => mockTransport . onclose ?.( ) ) . not . toThrow ( ) ;
170
170
} ) ;
171
171
} ) ;
172
+
173
+ describe ( 'Span Creation & Semantic Conventions' , ( ) => {
174
+ let mockMcpServer : ReturnType < typeof createMockMcpServer > ;
175
+ let wrappedMcpServer : ReturnType < typeof createMockMcpServer > ;
176
+ let mockTransport : ReturnType < typeof createMockTransport > ;
177
+
178
+ beforeEach ( async ( ) => {
179
+ mockMcpServer = createMockMcpServer ( ) ;
180
+ wrappedMcpServer = wrapMcpServerWithSentry ( mockMcpServer ) ;
181
+ mockTransport = createMockTransport ( ) ;
182
+ mockTransport . sessionId = 'test-session-123' ;
183
+
184
+ await wrappedMcpServer . connect ( mockTransport ) ;
185
+ } ) ;
186
+
187
+ it ( 'should create spans with correct MCP server semantic attributes for tool operations' , ( ) => {
188
+ const jsonRpcRequest = {
189
+ jsonrpc : '2.0' ,
190
+ method : 'tools/call' ,
191
+ id : 'req-1' ,
192
+ params : {
193
+ name : 'get-weather' ,
194
+ arguments : {
195
+ location : 'Seattle, WA' ,
196
+ units : 'metric'
197
+ }
198
+ }
199
+ } ;
200
+
201
+ const extraWithClientInfo = {
202
+ requestInfo : {
203
+ remoteAddress : '192.168.1.100' ,
204
+ remotePort : 54321
205
+ }
206
+ } ;
207
+
208
+ mockTransport . onmessage ?.( jsonRpcRequest , extraWithClientInfo ) ;
209
+
210
+ expect ( tracingModule . startSpan ) . toHaveBeenCalledWith (
211
+ expect . objectContaining ( {
212
+ name : 'tools/call get-weather' ,
213
+ op : 'mcp.server' ,
214
+ forceTransaction : true ,
215
+ attributes : expect . objectContaining ( {
216
+ // Required
217
+ 'mcp.method.name' : 'tools/call' ,
218
+ // Conditionally Required (tool operation)
219
+ 'mcp.tool.name' : 'get-weather' ,
220
+ 'mcp.request.id' : 'req-1' ,
221
+ // Recommended
222
+ 'mcp.session.id' : 'test-session-123' ,
223
+ 'client.address' : '192.168.1.100' ,
224
+ 'client.port' : 54321 ,
225
+ // Transport attributes
226
+ 'mcp.transport' : 'http' ,
227
+ 'network.transport' : 'tcp' ,
228
+ 'network.protocol.version' : '2.0' ,
229
+ // Tool arguments (JSON-stringified)
230
+ 'mcp.request.argument.location' : '"Seattle, WA"' ,
231
+ 'mcp.request.argument.units' : '"metric"' ,
232
+ // Sentry-specific
233
+ 'sentry.origin' : 'auto.function.mcp_server' ,
234
+ } ) ,
235
+ } ) ,
236
+ expect . any ( Function )
237
+ ) ;
238
+ } ) ;
239
+
240
+ it ( 'should create spans with correct attributes for resource operations' , ( ) => {
241
+ const jsonRpcRequest = {
242
+ jsonrpc : '2.0' ,
243
+ method : 'resources/read' ,
244
+ id : 'req-2' ,
245
+ params : { uri : 'file:///docs/api.md' }
246
+ } ;
247
+
248
+ mockTransport . onmessage ?.( jsonRpcRequest , { } ) ;
249
+
250
+ expect ( tracingModule . startSpan ) . toHaveBeenCalledWith (
251
+ expect . objectContaining ( {
252
+ name : 'resources/read file:///docs/api.md' ,
253
+ op : 'mcp.server' ,
254
+ attributes : expect . objectContaining ( {
255
+ // Required
256
+ 'mcp.method.name' : 'resources/read' ,
257
+ // Conditionally Required (resource operation)
258
+ 'mcp.resource.uri' : 'file:///docs/api.md' ,
259
+ 'mcp.request.id' : 'req-2' ,
260
+ // Recommended
261
+ 'mcp.session.id' : 'test-session-123' ,
262
+ } ) ,
263
+ } ) ,
264
+ expect . any ( Function )
265
+ ) ;
266
+ } ) ;
267
+
268
+ it ( 'should create spans with correct attributes for prompt operations' , ( ) => {
269
+ const jsonRpcRequest = {
270
+ jsonrpc : '2.0' ,
271
+ method : 'prompts/get' ,
272
+ id : 'req-3' ,
273
+ params : { name : 'analyze-code' }
274
+ } ;
275
+
276
+ mockTransport . onmessage ?.( jsonRpcRequest , { } ) ;
277
+
278
+ expect ( tracingModule . startSpan ) . toHaveBeenCalledWith (
279
+ expect . objectContaining ( {
280
+ name : 'prompts/get analyze-code' ,
281
+ op : 'mcp.server' ,
282
+ attributes : expect . objectContaining ( {
283
+ // Required
284
+ 'mcp.method.name' : 'prompts/get' ,
285
+ // Conditionally Required (prompt operation)
286
+ 'mcp.prompt.name' : 'analyze-code' ,
287
+ 'mcp.request.id' : 'req-3' ,
288
+ // Recommended
289
+ 'mcp.session.id' : 'test-session-123' ,
290
+ } ) ,
291
+ } ) ,
292
+ expect . any ( Function )
293
+ ) ;
294
+ } ) ;
295
+
296
+ it ( 'should create spans with correct attributes for notifications (no request id)' , ( ) => {
297
+ const jsonRpcNotification = {
298
+ jsonrpc : '2.0' ,
299
+ method : 'notifications/tools/list_changed' ,
300
+ params : { }
301
+ } ;
302
+
303
+ mockTransport . onmessage ?.( jsonRpcNotification , { } ) ;
304
+
305
+ expect ( tracingModule . startSpan ) . toHaveBeenCalledWith (
306
+ expect . objectContaining ( {
307
+ name : 'notifications/tools/list_changed' ,
308
+ op : 'mcp.server' ,
309
+ attributes : expect . objectContaining ( {
310
+ // Required
311
+ 'mcp.method.name' : 'notifications/tools/list_changed' ,
312
+ // Recommended
313
+ 'mcp.session.id' : 'test-session-123' ,
314
+ // Notification-specific
315
+ 'mcp.notification.direction' : 'client_to_server' ,
316
+ // Sentry-specific
317
+ 'sentry.origin' : 'auto.mcp.notification' ,
318
+ } ) ,
319
+ } ) ,
320
+ expect . any ( Function )
321
+ ) ;
322
+
323
+ // Should not include mcp.request.id for notifications
324
+ const callArgs = vi . mocked ( tracingModule . startSpan ) . mock . calls [ 0 ] ;
325
+ expect ( callArgs ) . toBeDefined ( ) ;
326
+ const attributes = callArgs ?. [ 0 ] ?. attributes ;
327
+ expect ( attributes ) . not . toHaveProperty ( 'mcp.request.id' ) ;
328
+ } ) ;
329
+
330
+ it ( 'should create spans for list operations without target in name' , ( ) => {
331
+ const jsonRpcRequest = {
332
+ jsonrpc : '2.0' ,
333
+ method : 'tools/list' ,
334
+ id : 'req-4' ,
335
+ params : { }
336
+ } ;
337
+
338
+ mockTransport . onmessage ?.( jsonRpcRequest , { } ) ;
339
+
340
+ expect ( tracingModule . startSpan ) . toHaveBeenCalledWith (
341
+ expect . objectContaining ( {
342
+ name : 'tools/list' ,
343
+ op : 'mcp.server' ,
344
+ attributes : expect . objectContaining ( {
345
+ 'mcp.method.name' : 'tools/list' ,
346
+ 'mcp.request.id' : 'req-4' ,
347
+ 'mcp.session.id' : 'test-session-123' ,
348
+ } ) ,
349
+ } ) ,
350
+ expect . any ( Function )
351
+ ) ;
352
+ } ) ;
353
+
354
+
355
+ } ) ;
172
356
} ) ;
173
357
174
358
// Test helpers
0 commit comments